Debugger/DebuggerInterfacePython2.py

branch
jsonrpc
changeset 5132
a094eee9f862
parent 5059
b619cb765507
child 5133
b7fe69c6cb1c
equal deleted inserted replaced
5131:889ed5ff7a68 5132:a094eee9f862
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Python debugger interface for the debug server.
8 """
9
10 from __future__ import unicode_literals
11
12 import sys
13 import os
14
15 from PyQt5.QtCore import QObject, QTextCodec, QProcess, QProcessEnvironment, \
16 QTimer
17 from PyQt5.QtWidgets import QInputDialog
18
19 from E5Gui.E5Application import e5App
20 from E5Gui import E5MessageBox
21
22 from . import DebugClientCapabilities
23
24 import Preferences
25 import Utilities
26
27 from eric6config import getConfig
28
29
30 ClientDefaultCapabilities = DebugClientCapabilities.HasAll
31
32
33 class DebuggerInterfacePython2(QObject):
34 """
35 Class implementing the Python 2 debugger interface for the debug server.
36 """
37 def __init__(self, debugServer, passive):
38 """
39 Constructor
40
41 @param debugServer reference to the debug server (DebugServer)
42 @param passive flag indicating passive connection mode (boolean)
43 """
44 super(DebuggerInterfacePython2, self).__init__()
45
46 self.__isNetworked = True
47 self.__autoContinue = False
48
49 self.debugServer = debugServer
50 self.passive = passive
51 self.process = None
52
53 self.qsock = None
54 self.queue = []
55
56 # set default values for capabilities of clients
57 self.clientCapabilities = ClientDefaultCapabilities
58
59 # set translation function
60 self.translate = self.__identityTranslation
61
62 self.codec = QTextCodec.codecForName(
63 Preferences.getSystem("StringEncoding"))
64
65 if passive:
66 # set translation function
67 if Preferences.getDebugger("PathTranslation"):
68 self.translateRemote = \
69 Preferences.getDebugger("PathTranslationRemote")
70 self.translateLocal = \
71 Preferences.getDebugger("PathTranslationLocal")
72 self.translate = self.__remoteTranslation
73 else:
74 self.translate = self.__identityTranslation
75
76 # attribute to remember the name of the executed script
77 self.__scriptName = ""
78
79 def __identityTranslation(self, fn, remote2local=True):
80 """
81 Private method to perform the identity path translation.
82
83 @param fn filename to be translated (string)
84 @param remote2local flag indicating the direction of translation
85 (False = local to remote, True = remote to local [default])
86 @return translated filename (string)
87 """
88 return fn
89
90 def __remoteTranslation(self, fn, remote2local=True):
91 """
92 Private method to perform the path translation.
93
94 @param fn filename to be translated (string)
95 @param remote2local flag indicating the direction of translation
96 (False = local to remote, True = remote to local [default])
97 @return translated filename (string)
98 """
99 if remote2local:
100 return fn.replace(self.translateRemote, self.translateLocal)
101 else:
102 return fn.replace(self.translateLocal, self.translateRemote)
103
104 def __startProcess(self, program, arguments, environment=None):
105 """
106 Private method to start the debugger client process.
107
108 @param program name of the executable to start (string)
109 @param arguments arguments to be passed to the program (list of string)
110 @param environment dictionary of environment settings to pass
111 (dict of string)
112 @return the process object (QProcess) or None
113 """
114 proc = QProcess()
115 if environment is not None:
116 env = QProcessEnvironment()
117 for key, value in list(environment.items()):
118 env.insert(key, value)
119 proc.setProcessEnvironment(env)
120 args = []
121 for arg in arguments:
122 args.append(arg)
123 proc.start(program, args)
124 if not proc.waitForStarted(10000):
125 proc = None
126
127 return proc
128
129 def startRemote(self, port, runInConsole):
130 """
131 Public method to start a remote Python interpreter.
132
133 @param port portnumber the debug server is listening on (integer)
134 @param runInConsole flag indicating to start the debugger in a
135 console window (boolean)
136 @return client process object (QProcess), a flag to indicate
137 a network connection (boolean) and the name of the interpreter
138 in case of a local execution (string)
139 """
140 interpreter = Preferences.getDebugger("PythonInterpreter")
141 if interpreter == "":
142 E5MessageBox.critical(
143 None,
144 self.tr("Start Debugger"),
145 self.tr(
146 """<p>No Python2 interpreter configured.</p>"""))
147 return None, False, ""
148
149 debugClientType = Preferences.getDebugger("DebugClientType")
150 if debugClientType == "standard":
151 debugClient = os.path.join(getConfig('ericDir'),
152 "DebugClients", "Python",
153 "DebugClient.py")
154 elif debugClientType == "threaded":
155 debugClient = os.path.join(getConfig('ericDir'),
156 "DebugClients", "Python",
157 "DebugClientThreads.py")
158 else:
159 debugClient = Preferences.getDebugger("DebugClient")
160 if debugClient == "":
161 debugClient = os.path.join(sys.path[0],
162 "DebugClients", "Python",
163 "DebugClient.py")
164
165 redirect = str(Preferences.getDebugger("PythonRedirect"))
166 noencoding = Preferences.getDebugger("PythonNoEncoding") and \
167 '--no-encoding' or ''
168
169 if Preferences.getDebugger("RemoteDbgEnabled"):
170 ipaddr = self.debugServer.getHostAddress(False)
171 rexec = Preferences.getDebugger("RemoteExecution")
172 rhost = Preferences.getDebugger("RemoteHost")
173 if rhost == "":
174 rhost = "localhost"
175 if rexec:
176 args = Utilities.parseOptionString(rexec) + \
177 [rhost, interpreter, debugClient,
178 noencoding, str(port), redirect, ipaddr]
179 args[0] = Utilities.getExecutablePath(args[0])
180 process = self.__startProcess(args[0], args[1:])
181 if process is None:
182 E5MessageBox.critical(
183 None,
184 self.tr("Start Debugger"),
185 self.tr(
186 """<p>The debugger backend could not be"""
187 """ started.</p>"""))
188
189 # set translation function
190 if Preferences.getDebugger("PathTranslation"):
191 self.translateRemote = \
192 Preferences.getDebugger("PathTranslationRemote")
193 self.translateLocal = \
194 Preferences.getDebugger("PathTranslationLocal")
195 self.translate = self.__remoteTranslation
196 else:
197 self.translate = self.__identityTranslation
198 return process, self.__isNetworked, ""
199
200 # set translation function
201 self.translate = self.__identityTranslation
202
203 # setup the environment for the debugger
204 if Preferences.getDebugger("DebugEnvironmentReplace"):
205 clientEnv = {}
206 else:
207 clientEnv = os.environ.copy()
208 envlist = Utilities.parseEnvironmentString(
209 Preferences.getDebugger("DebugEnvironment"))
210 for el in envlist:
211 try:
212 key, value = el.split('=', 1)
213 if value.startswith('"') or value.startswith("'"):
214 value = value[1:-1]
215 clientEnv[str(key)] = str(value)
216 except ValueError:
217 pass
218
219 ipaddr = self.debugServer.getHostAddress(True)
220 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"):
221 ccmd = Preferences.getDebugger("ConsoleDbgCommand")
222 if ccmd:
223 args = Utilities.parseOptionString(ccmd) + \
224 [interpreter, os.path.abspath(debugClient),
225 noencoding, str(port), '0', ipaddr]
226 args[0] = Utilities.getExecutablePath(args[0])
227 process = self.__startProcess(args[0], args[1:], clientEnv)
228 if process is None:
229 E5MessageBox.critical(
230 None,
231 self.tr("Start Debugger"),
232 self.tr(
233 """<p>The debugger backend could not be"""
234 """ started.</p>"""))
235 return process, self.__isNetworked, interpreter
236
237 process = self.__startProcess(
238 interpreter,
239 [debugClient, noencoding, str(port), redirect, ipaddr],
240 clientEnv)
241 if process is None:
242 E5MessageBox.critical(
243 None,
244 self.tr("Start Debugger"),
245 self.tr(
246 """<p>The debugger backend could not be started.</p>"""))
247 return process, self.__isNetworked, interpreter
248
249 def startRemoteForProject(self, port, runInConsole):
250 """
251 Public method to start a remote Python interpreter for a project.
252
253 @param port portnumber the debug server is listening on (integer)
254 @param runInConsole flag indicating to start the debugger in a
255 console window (boolean)
256 @return client process object (QProcess), a flag to indicate
257 a network connection (boolean) and the name of the interpreter
258 in case of a local execution (string)
259 """
260 project = e5App().getObject("Project")
261 if not project.isDebugPropertiesLoaded():
262 return None, self.__isNetworked, ""
263
264 # start debugger with project specific settings
265 interpreter = project.getDebugProperty("INTERPRETER")
266 debugClient = project.getDebugProperty("DEBUGCLIENT")
267
268 redirect = str(project.getDebugProperty("REDIRECT"))
269 noencoding = \
270 project.getDebugProperty("NOENCODING") and '--no-encoding' or ''
271
272 if project.getDebugProperty("REMOTEDEBUGGER"):
273 ipaddr = self.debugServer.getHostAddress(False)
274 rexec = project.getDebugProperty("REMOTECOMMAND")
275 rhost = project.getDebugProperty("REMOTEHOST")
276 if rhost == "":
277 rhost = "localhost"
278 if rexec:
279 args = Utilities.parseOptionString(rexec) + \
280 [rhost, interpreter, os.path.abspath(debugClient),
281 noencoding, str(port), redirect, ipaddr]
282 args[0] = Utilities.getExecutablePath(args[0])
283 process = self.__startProcess(args[0], args[1:])
284 if process is None:
285 E5MessageBox.critical(
286 None,
287 self.tr("Start Debugger"),
288 self.tr(
289 """<p>The debugger backend could not be"""
290 """ started.</p>"""))
291 # set translation function
292 if project.getDebugProperty("PATHTRANSLATION"):
293 self.translateRemote = \
294 project.getDebugProperty("REMOTEPATH")
295 self.translateLocal = \
296 project.getDebugProperty("LOCALPATH")
297 self.translate = self.__remoteTranslation
298 else:
299 self.translate = self.__identityTranslation
300 return process, self.__isNetworked, ""
301
302 # set translation function
303 self.translate = self.__identityTranslation
304
305 # setup the environment for the debugger
306 if project.getDebugProperty("ENVIRONMENTOVERRIDE"):
307 clientEnv = {}
308 else:
309 clientEnv = os.environ.copy()
310 envlist = Utilities.parseEnvironmentString(
311 project.getDebugProperty("ENVIRONMENTSTRING"))
312 for el in envlist:
313 try:
314 key, value = el.split('=', 1)
315 if value.startswith('"') or value.startswith("'"):
316 value = value[1:-1]
317 clientEnv[str(key)] = str(value)
318 except ValueError:
319 pass
320
321 ipaddr = self.debugServer.getHostAddress(True)
322 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"):
323 ccmd = project.getDebugProperty("CONSOLECOMMAND") or \
324 Preferences.getDebugger("ConsoleDbgCommand")
325 if ccmd:
326 args = Utilities.parseOptionString(ccmd) + \
327 [interpreter, os.path.abspath(debugClient),
328 noencoding, str(port), '0', ipaddr]
329 args[0] = Utilities.getExecutablePath(args[0])
330 process = self.__startProcess(args[0], args[1:], clientEnv)
331 if process is None:
332 E5MessageBox.critical(
333 None,
334 self.tr("Start Debugger"),
335 self.tr(
336 """<p>The debugger backend could not be"""
337 """ started.</p>"""))
338 return process, self.__isNetworked, interpreter
339
340 process = self.__startProcess(
341 interpreter,
342 [debugClient, noencoding, str(port), redirect, ipaddr],
343 clientEnv)
344 if process is None:
345 E5MessageBox.critical(
346 None,
347 self.tr("Start Debugger"),
348 self.tr(
349 """<p>The debugger backend could not be started.</p>"""))
350 return process, self.__isNetworked, interpreter
351
352 def getClientCapabilities(self):
353 """
354 Public method to retrieve the debug clients capabilities.
355
356 @return debug client capabilities (integer)
357 """
358 return self.clientCapabilities
359
360 def newConnection(self, sock):
361 """
362 Public slot to handle a new connection.
363
364 @param sock reference to the socket object (QTcpSocket)
365 @return flag indicating success (boolean)
366 """
367 # If we already have a connection, refuse this one. It will be closed
368 # automatically.
369 if self.qsock is not None:
370 return False
371
372 sock.disconnected.connect(self.debugServer.startClient)
373 sock.readyRead.connect(self.__parseClientLine)
374
375 self.qsock = sock
376
377 # Get the remote clients capabilities
378 self.remoteCapabilities()
379 return True
380
381 def flush(self):
382 """
383 Public slot to flush the queue.
384 """
385 # Send commands that were waiting for the connection.
386 for cmd in self.queue:
387 self.qsock.write(cmd.encode('utf8', 'backslashreplace'))
388
389 self.queue = []
390
391 def shutdown(self):
392 """
393 Public method to cleanly shut down.
394
395 It closes our socket and shuts down
396 the debug client. (Needed on Win OS)
397 """
398 if self.qsock is None:
399 return
400
401 # do not want any slots called during shutdown
402 self.qsock.disconnected.disconnect(self.debugServer.startClient)
403 self.qsock.readyRead.disconnect(self.__parseClientLine)
404
405 # close down socket, and shut down client as well.
406 self.__sendJsonCommand("RequestShutdown", {})
407 self.qsock.flush()
408 self.qsock.close()
409
410 # reinitialize
411 self.qsock = None
412 self.queue = []
413
414 def isConnected(self):
415 """
416 Public method to test, if a debug client has connected.
417
418 @return flag indicating the connection status (boolean)
419 """
420 return self.qsock is not None
421
422 def remoteEnvironment(self, env):
423 """
424 Public method to set the environment for a program to debug, run, ...
425
426 @param env environment settings (dictionary)
427 """
428 self.__sendJsonCommand("RequestEnvironment", {"environment": env})
429
430 def remoteLoad(self, fn, argv, wd, traceInterpreter=False,
431 autoContinue=True, autoFork=False, forkChild=False):
432 """
433 Public method to load a new program to debug.
434
435 @param fn the filename to debug (string)
436 @param argv the commandline arguments to pass to the program (string)
437 @param wd the working directory for the program (string)
438 @keyparam traceInterpreter flag indicating if the interpreter library
439 should be traced as well (boolean)
440 @keyparam autoContinue flag indicating, that the debugger should not
441 stop at the first executable line (boolean)
442 @keyparam autoFork flag indicating the automatic fork mode (boolean)
443 @keyparam forkChild flag indicating to debug the child after forking
444 (boolean)
445 """
446 self.__autoContinue = autoContinue
447 self.__scriptName = os.path.abspath(fn)
448
449 wd = self.translate(wd, False)
450 fn = self.translate(os.path.abspath(fn), False)
451 self.__sendJsonCommand("RequestLoad", {
452 "workdir": wd,
453 "filename": fn,
454 "argv": Utilities.parseOptionString(argv),
455 "traceInterpreter": traceInterpreter,
456 "autofork": autoFork,
457 "forkChild": forkChild,
458 })
459
460 def remoteRun(self, fn, argv, wd, autoFork=False, forkChild=False):
461 """
462 Public method to load a new program to run.
463
464 @param fn the filename to run (string)
465 @param argv the commandline arguments to pass to the program (string)
466 @param wd the working directory for the program (string)
467 @keyparam autoFork flag indicating the automatic fork mode (boolean)
468 @keyparam forkChild flag indicating to debug the child after forking
469 (boolean)
470 """
471 self.__scriptName = os.path.abspath(fn)
472
473 wd = self.translate(wd, False)
474 fn = self.translate(os.path.abspath(fn), False)
475 self.__sendJsonCommand("RequestRun", {
476 "workdir": wd,
477 "filename": fn,
478 "argv": Utilities.parseOptionString(argv),
479 "autofork": autoFork,
480 "forkChild": forkChild,
481 })
482
483 def remoteCoverage(self, fn, argv, wd, erase=False):
484 """
485 Public method to load a new program to collect coverage data.
486
487 @param fn the filename to run (string)
488 @param argv the commandline arguments to pass to the program (string)
489 @param wd the working directory for the program (string)
490 @keyparam erase flag indicating that coverage info should be
491 cleared first (boolean)
492 """
493 self.__scriptName = os.path.abspath(fn)
494
495 wd = self.translate(wd, False)
496 fn = self.translate(os.path.abspath(fn), False)
497 self.__sendJsonCommand("RequestCoverage", {
498 "workdir": wd,
499 "filename": fn,
500 "argv": Utilities.parseOptionString(argv),
501 "erase": erase,
502 })
503
504 def remoteProfile(self, fn, argv, wd, erase=False):
505 """
506 Public method to load a new program to collect profiling data.
507
508 @param fn the filename to run (string)
509 @param argv the commandline arguments to pass to the program (string)
510 @param wd the working directory for the program (string)
511 @keyparam erase flag indicating that timing info should be cleared
512 first (boolean)
513 """
514 self.__scriptName = os.path.abspath(fn)
515
516 wd = self.translate(wd, False)
517 fn = self.translate(os.path.abspath(fn), False)
518 self.__sendJsonCommand("RequestProfile", {
519 "workdir": wd,
520 "filename": fn,
521 "argv": Utilities.parseOptionString(argv),
522 "erase": erase,
523 })
524
525 def remoteStatement(self, stmt):
526 """
527 Public method to execute a Python statement.
528
529 @param stmt the Python statement to execute (string). It
530 should not have a trailing newline.
531 """
532 self.__sendJsonCommand("ExecuteStatement", {
533 "statement": stmt,
534 })
535
536 def remoteStep(self):
537 """
538 Public method to single step the debugged program.
539 """
540 self.__sendJsonCommand("RequestStep", {})
541
542 def remoteStepOver(self):
543 """
544 Public method to step over the debugged program.
545 """
546 self.__sendJsonCommand("RequestStepOver", {})
547
548 def remoteStepOut(self):
549 """
550 Public method to step out the debugged program.
551 """
552 self.__sendJsonCommand("RequestStepOut", {})
553
554 def remoteStepQuit(self):
555 """
556 Public method to stop the debugged program.
557 """
558 self.__sendJsonCommand("RequestStepQuit", {})
559
560 def remoteContinue(self, special=False):
561 """
562 Public method to continue the debugged program.
563
564 @param special flag indicating a special continue operation (boolean)
565 """
566 self.__sendJsonCommand("RequestContinue", {
567 "special": special,
568 })
569
570 def remoteBreakpoint(self, fn, line, setBreakpoint, cond=None, temp=False):
571 """
572 Public method to set or clear a breakpoint.
573
574 @param fn filename the breakpoint belongs to (string)
575 @param line linenumber of the breakpoint (int)
576 @param setBreakpoint flag indicating setting or resetting a
577 breakpoint (boolean)
578 @param cond condition of the breakpoint (string)
579 @param temp flag indicating a temporary breakpoint (boolean)
580 """
581 self.__sendJsonCommand("RequestBreakpoint", {
582 "filename": self.translate(fn, False),
583 "line": line,
584 "temporary": temp,
585 "setBreakpoint": setBreakpoint,
586 "condition": cond,
587 })
588
589 def remoteBreakpointEnable(self, fn, line, enable):
590 """
591 Public method to enable or disable a breakpoint.
592
593 @param fn filename the breakpoint belongs to (string)
594 @param line linenumber of the breakpoint (int)
595 @param enable flag indicating enabling or disabling a breakpoint
596 (boolean)
597 """
598 self.__sendJsonCommand("RequestBreakpointEnable", {
599 "filename": self.translate(fn, False),
600 "line": line,
601 "enable": enable,
602 })
603
604 def remoteBreakpointIgnore(self, fn, line, count):
605 """
606 Public method to ignore a breakpoint the next couple of occurrences.
607
608 @param fn filename the breakpoint belongs to (string)
609 @param line linenumber of the breakpoint (int)
610 @param count number of occurrences to ignore (int)
611 """
612 self.__sendJsonCommand("RequestBreakpointIgnore", {
613 "filename": self.translate(fn, False),
614 "line": line,
615 "count": count,
616 })
617
618 def remoteWatchpoint(self, cond, setWatch, temp=False):
619 """
620 Public method to set or clear a watch expression.
621
622 @param cond expression of the watch expression (string)
623 @param setWatch flag indicating setting or resetting a watch expression
624 (boolean)
625 @param temp flag indicating a temporary watch expression (boolean)
626 """
627 # cond is combination of cond and special (s. watch expression viewer)
628 self.__sendJsonCommand("RequestWatch", {
629 "temporary": temp,
630 "setWatch": setWatch,
631 "condition": cond,
632 })
633
634 def remoteWatchpointEnable(self, cond, enable):
635 """
636 Public method to enable or disable a watch expression.
637
638 @param cond expression of the watch expression (string)
639 @param enable flag indicating enabling or disabling a watch expression
640 (boolean)
641 """
642 # cond is combination of cond and special (s. watch expression viewer)
643 self.__sendJsonCommand("RequestWatchEnable", {
644 "condition": cond,
645 "enable": enable,
646 })
647
648 def remoteWatchpointIgnore(self, cond, count):
649 """
650 Public method to ignore a watch expression the next couple of
651 occurrences.
652
653 @param cond expression of the watch expression (string)
654 @param count number of occurrences to ignore (int)
655 """
656 # cond is combination of cond and special (s. watch expression viewer)
657 self.__sendJsonCommand("RequestWatchIgnore", {
658 "condition": cond,
659 "count": count,
660 })
661
662 def remoteRawInput(self, s):
663 """
664 Public method to send the raw input to the debugged program.
665
666 @param s the raw input (string)
667 """
668 self.__sendJsonCommand("RawInput", {
669 "input": s,
670 })
671
672 def remoteThreadList(self):
673 """
674 Public method to request the list of threads from the client.
675 """
676 self.__sendJsonCommand("RequestThreadList", {})
677
678 def remoteSetThread(self, tid):
679 """
680 Public method to request to set the given thread as current thread.
681
682 @param tid id of the thread (integer)
683 """
684 self.__sendJsonCommand("RequestThreadSet", {
685 "threadID": tid,
686 })
687
688 def remoteClientVariables(self, scope, filter, framenr=0):
689 """
690 Public method to request the variables of the debugged program.
691
692 @param scope the scope of the variables (0 = local, 1 = global)
693 @param filter list of variable types to filter out (list of int)
694 @param framenr framenumber of the variables to retrieve (int)
695 """
696 self.__sendJsonCommand("RequestVariables", {
697 "frameNumber": framenr,
698 "scope": scope,
699 "filters": filter,
700 })
701
702 def remoteClientVariable(self, scope, filter, var, framenr=0):
703 """
704 Public method to request the variables of the debugged program.
705
706 @param scope the scope of the variables (0 = local, 1 = global)
707 @param filter list of variable types to filter out (list of int)
708 @param var list encoded name of variable to retrieve (string)
709 @param framenr framenumber of the variables to retrieve (int)
710 """
711 self.__sendJsonCommand("RequestVariable", {
712 "variable": var,
713 "frameNumber": framenr,
714 "scope": scope,
715 "filters": filter,
716 })
717
718 def remoteClientSetFilter(self, scope, filter):
719 """
720 Public method to set a variables filter list.
721
722 @param scope the scope of the variables (0 = local, 1 = global)
723 @param filter regexp string for variable names to filter out (string)
724 """
725 self.__sendJsonCommand("RequestSetFilter", {
726 "scope": scope,
727 "filter": filter,
728 })
729
730 def setCallTraceEnabled(self, on):
731 """
732 Public method to set the call trace state.
733
734 @param on flag indicating to enable the call trace function (boolean)
735 """
736 self.__sendJsonCommand("RequestCallTrace", {
737 "enable": on,
738 })
739
740 def remoteBanner(self):
741 """
742 Public slot to get the banner info of the remote client.
743 """
744 self.__sendJsonCommand("RequestBanner", {})
745
746 def remoteCapabilities(self):
747 """
748 Public slot to get the debug clients capabilities.
749 """
750 self.__sendJsonCommand("RequestCapabilities", {})
751
752 def remoteCompletion(self, text):
753 """
754 Public slot to get the a list of possible commandline completions
755 from the remote client.
756
757 @param text the text to be completed (string)
758 """
759 self.__sendJsonCommand("RequestCompletion", {
760 "text": text,
761 })
762
763 def remoteUTPrepare(self, fn, tn, tfn, failed, cov, covname, coverase):
764 """
765 Public method to prepare a new unittest run.
766
767 @param fn the filename to load (string)
768 @param tn the testname to load (string)
769 @param tfn the test function name to load tests from (string)
770 @param failed list of failed test, if only failed test should be run
771 (list of strings)
772 @param cov flag indicating collection of coverage data is requested
773 (boolean)
774 @param covname filename to be used to assemble the coverage caches
775 filename (string)
776 @param coverase flag indicating erasure of coverage data is requested
777 (boolean)
778 """
779 self.__scriptName = os.path.abspath(fn)
780
781 fn = self.translate(os.path.abspath(fn), False)
782 self.__sendJsonCommand("RequestUTPrepare", {
783 "filename": fn,
784 "testname": tn,
785 "testfunctionname": tfn,
786 "failed": failed,
787 "coverage": cov,
788 "coveragefile": covname,
789 "coverageerase": coverase,
790 })
791
792 def remoteUTRun(self):
793 """
794 Public method to start a unittest run.
795 """
796 self.__sendJsonCommand("RequestUTRun", {})
797
798 def remoteUTStop(self):
799 """
800 Public method to stop a unittest run.
801 """
802 self.__sendJsonCommand("RequestUTStop", {})
803
804 def __askForkTo(self):
805 """
806 Private method to ask the user which branch of a fork to follow.
807 """
808 selections = [self.tr("Parent Process"),
809 self.tr("Child process")]
810 res, ok = QInputDialog.getItem(
811 None,
812 self.tr("Client forking"),
813 self.tr("Select the fork branch to follow."),
814 selections,
815 0, False)
816 if not ok or res == selections[0]:
817 self.__sendJsonCommand("ResponseForkTo", {
818 "target": "parent",
819 })
820 else:
821 self.__sendJsonCommand("ResponseForkTo", {
822 "target": "child",
823 })
824
825 def __parseClientLine(self):
826 """
827 Private method to handle data from the client.
828 """
829 while self.qsock and self.qsock.canReadLine():
830 qs = self.qsock.readLine()
831 if self.codec is not None:
832 line = self.codec.toUnicode(qs)
833 else:
834 line = bytes(qs).decode()
835
836 ## print("Server: ", line) ##debug
837
838 self.__handleJsonCommand(line)
839 continue
840
841 def __handleJsonCommand(self, jsonStr):
842 """
843 Private method to handle a command or response serialized as a
844 JSON string.
845
846 @param jsonStr string containing the command or response received
847 from the debug backend
848 @type str
849 """
850 import json
851
852 try:
853 commandDict = json.loads(jsonStr.strip())
854 except json.JSONDecodeError as err:
855 # TODO: implement real error handling
856 ##print(str(err))
857 return
858
859 method = commandDict["method"]
860 params = commandDict["params"]
861
862 if method == "ClientOutput":
863 self.debugServer.signalClientOutput(params["text"])
864
865 elif method in ["ResponseLine", "ResponseStack"]:
866 for s in params["stack"]:
867 s[0] = self.translate(s[0], True)
868 cf = params["stack"][0]
869 if self.__autoContinue:
870 self.__autoContinue = False
871 QTimer.singleShot(0, self.remoteContinue)
872 else:
873 self.debugServer.signalClientLine(
874 cf[0], int(cf[1]),
875 method == "ResponseStack")
876 self.debugServer.signalClientStack(params["stack"])
877
878 elif method == "CallTrace":
879 isCall = params["event"].lower() == "c"
880 fromFile, fromLineno, fromFunc = params["from"].rsplit(":", 2)
881 toFile, toLineno, toFunc = params["to"].rsplit(":", 2)
882 self.debugServer.signalClientCallTrace(
883 isCall,
884 fromFile, fromLineno, fromFunc,
885 toFile, toLineno, toFunc)
886
887 elif method == "ResponseVariables":
888 self.debugServer.signalClientVariables(
889 params["scope"], params["variables"])
890
891 elif method == "ResponseVariable":
892 self.debugServer.signalClientVariable(
893 params["scope"], [params["variable"]] + params["variables"])
894
895 elif method == "ResponseThreadList":
896 self.debugServer.signalClientThreadList(
897 params["currentID"], params["threadList"])
898
899 elif method == "ResponseThreadSet":
900 self.debugServer.signalClientThreadSet()
901
902 elif method == "ResponseCapabilities":
903 self.clientCapabilities = params["capabilities"]
904 self.debugServer.signalClientCapabilities(
905 params["capabilities"], params["clientType"])
906
907 elif method == "ResponseBanner":
908 self.debugServer.signalClientBanner(
909 params["version"],
910 params["platform"],
911 params["dbgclient"])
912
913 elif method == "ResponseOK":
914 self.debugServer.signalClientStatement(False)
915
916 elif method == "ResponseContinue":
917 self.debugServer.signalClientStatement(True)
918
919 elif method == "RequestRaw":
920 self.debugServer.signalClientRawInput(
921 params["prompt"], params["echo"])
922
923 elif method == "ResponseBPConditionError":
924 fn = self.translate(params["filename"], True)
925 self.debugServer.signalClientBreakConditionError(
926 fn, params["line"])
927
928 elif method == "ResponseClearBreakpoint":
929 fn = self.translate(params["filename"], True)
930 self.debugServer.signalClientClearBreak(fn, params["line"])
931
932 elif method == "ResponseWatchConditionError":
933 self.debugServer.signalClientWatchConditionError(
934 params["condition"])
935
936 elif method == "ResponseClearWatch":
937 self.debugServer.signalClientClearWatch(params["condition"])
938
939 elif method == "ResponseException":
940 if params:
941 exctype = params["type"]
942 excmessage = params["message"]
943 stack = params["stack"]
944 if stack:
945 for stackEntry in stack:
946 stackEntry[0] = self.translate(stackEntry[0], True)
947 if stack[0] and stack[0][0] == "<string>":
948 for stackEntry in stack:
949 if stackEntry[0] == "<string>":
950 stackEntry[0] = self.__scriptName
951 else:
952 break
953 else:
954 exctype = ''
955 excmessage = ''
956 stack = []
957
958 self.debugServer.signalClientException(
959 exctype, excmessage, stack)
960
961 elif method == "ResponseSyntax":
962 self.debugServer.signalClientSyntaxError(
963 params["message"], self.translate(params["filename"], True),
964 params["linenumber"], params["characternumber"])
965
966 elif method == "ResponseSignal":
967 self.debugServer.signalClientSignal(
968 params["message"], self.translate(params["filename"], True),
969 params["linenumber"], params["function"], params["arguments"])
970
971 elif method == "ResponseExit":
972 self.__scriptName = ""
973 # TODO: combine these into signalClientExit
974 self.debugServer.signalClientExit(params["status"])
975 if params["message"]:
976 self.debugServer.signalClientOutput(params["message"])
977
978 elif method == "PassiveStartup":
979 self.debugServer.passiveStartUp(
980 self.translate(params["filename"], True), params["exceptions"])
981
982 elif method == "ResponseCompletion":
983 self.debugServer.signalClientCompletionList(
984 params["completions"], params["text"])
985
986 elif method == "ResponseUTPrepared":
987 self.debugServer.clientUtPrepared(
988 params["count"], params["exception"], params["message"])
989
990 elif method == "ResponseUTFinished":
991 self.debugServer.clientUtFinished()
992
993 elif method == "ResponseUTStartTest":
994 self.debugServer.clientUtStartTest(
995 params["testname"], params["description"])
996
997 elif method == "ResponseUTStopTest":
998 self.debugServer.clientUtStopTest()
999
1000 elif method == "ResponseUTTestFailed":
1001 self.debugServer.clientUtTestFailed(
1002 params["testname"], params["traceback"], params["id"])
1003
1004 elif method == "ResponseUTTestErrored":
1005 self.debugServer.clientUtTestErrored(
1006 params["testname"], params["traceback"], params["id"])
1007
1008 elif method == "ResponseUTTestSkipped":
1009 self.debugServer.clientUtTestSkipped(
1010 params["testname"], params["reason"], params["id"])
1011
1012 elif method == "ResponseUTTestFailedExpected":
1013 self.debugServer.clientUtTestFailedExpected(
1014 params["testname"], params["traceback"], params["id"])
1015
1016 elif method == "ResponseUTTestSucceededUnexpected":
1017 self.debugServer.clientUtTestSucceededUnexpected(
1018 params["testname"], params["id"])
1019
1020 elif method == "RequestForkTo":
1021 self.__askForkTo()
1022
1023 def __sendJsonCommand(self, command, params):
1024 """
1025 Private method to send a single command to the client.
1026
1027 @param command command name to be sent
1028 @type str
1029 @param params dictionary of named parameters for the command
1030 @type dict
1031 """
1032 import json
1033
1034 commandDict = {
1035 "jsonrpc": "2.0",
1036 "method": command,
1037 "params": params,
1038 }
1039 cmd = json.dumps(commandDict) + '\n'
1040 if self.qsock is not None:
1041 self.qsock.write(cmd.encode('utf8', 'backslashreplace'))
1042 else:
1043 self.queue.append(cmd)
1044
1045
1046 def createDebuggerInterfacePython2(debugServer, passive):
1047 """
1048 Module function to create a debugger interface instance.
1049
1050
1051 @param debugServer reference to the debug server
1052 @type DebugServer
1053 @param passive flag indicating passive connection mode
1054 @type bool
1055 @return instantiated debugger interface
1056 @rtype DebuggerInterfacePython
1057 """
1058 return DebuggerInterfacePython2(debugServer, passive)
1059
1060
1061 def getRegistryData():
1062 """
1063 Module function to get characterizing data for the debugger interface.
1064
1065 @return tuple containing client type, client capabilities, client file
1066 type associations and reference to creation function
1067 @rtype tuple of (str, int, list of str, function)
1068 """
1069 exts = []
1070 for ext in Preferences.getDebugger("PythonExtensions").split():
1071 if ext.startswith("."):
1072 exts.append(ext)
1073 else:
1074 exts.append(".{0}".format(ext))
1075
1076 if exts and Preferences.getDebugger("PythonInterpreter"):
1077 return ["Python2", ClientDefaultCapabilities, exts,
1078 createDebuggerInterfacePython2]
1079 else:
1080 return ["", 0, [], None]

eric ide

mercurial