DebugClients/Python2/DebugClientBase.py

branch
jsonrpc
changeset 5133
b7fe69c6cb1c
parent 5132
a094eee9f862
child 5134
4a4212a6f40c
equal deleted inserted replaced
5132:a094eee9f862 5133:b7fe69c6cb1c
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a debug client base class.
8 """
9
10 import sys
11 import socket
12 import select
13 import codeop
14 import traceback
15 import os
16 import time
17 import imp
18 import re
19 import atexit
20 import signal
21 import inspect
22
23
24 import DebugClientCapabilities
25 from DebugBase import setRecursionLimit, printerr # __IGNORE_WARNING__
26 from AsyncFile import AsyncFile, AsyncPendingWrite
27 from DebugConfig import ConfigVarTypeStrings
28 from FlexCompleter import Completer
29 from DebugUtilities import prepareJsonCommand
30
31
32 DebugClientInstance = None
33
34 ###############################################################################
35
36
37 def DebugClientRawInput(prompt="", echo=1):
38 """
39 Replacement for the standard raw_input builtin.
40
41 This function works with the split debugger.
42
43 @param prompt prompt to be shown. (string)
44 @param echo flag indicating echoing of the input (boolean)
45 @return result of the raw_input() call
46 """
47 if DebugClientInstance is None or not DebugClientInstance.redirect:
48 return DebugClientOrigRawInput(prompt)
49
50 return DebugClientInstance.raw_input(prompt, echo)
51
52 # Use our own raw_input().
53 try:
54 DebugClientOrigRawInput = __builtins__.__dict__['raw_input']
55 __builtins__.__dict__['raw_input'] = DebugClientRawInput
56 except (AttributeError, KeyError):
57 import __main__
58 DebugClientOrigRawInput = __main__.__builtins__.__dict__['raw_input']
59 __main__.__builtins__.__dict__['raw_input'] = DebugClientRawInput
60
61 ###############################################################################
62
63
64 def DebugClientInput(prompt=""):
65 """
66 Replacement for the standard input builtin.
67
68 This function works with the split debugger.
69
70 @param prompt prompt to be shown (string)
71 @return result of the input() call
72 """
73 if DebugClientInstance is None or DebugClientInstance.redirect == 0:
74 return DebugClientOrigInput(prompt)
75
76 return DebugClientInstance.input(prompt)
77
78 # Use our own input().
79 try:
80 DebugClientOrigInput = __builtins__.__dict__['input']
81 __builtins__.__dict__['input'] = DebugClientInput
82 except (AttributeError, KeyError):
83 import __main__
84 DebugClientOrigInput = __main__.__builtins__.__dict__['input']
85 __main__.__builtins__.__dict__['input'] = DebugClientInput
86
87 ###############################################################################
88
89
90 def DebugClientFork():
91 """
92 Replacement for the standard os.fork().
93
94 @return result of the fork() call
95 """
96 if DebugClientInstance is None:
97 return DebugClientOrigFork()
98
99 return DebugClientInstance.fork()
100
101 # use our own fork().
102 if 'fork' in dir(os):
103 DebugClientOrigFork = os.fork
104 os.fork = DebugClientFork
105
106 ###############################################################################
107
108
109 def DebugClientClose(fd):
110 """
111 Replacement for the standard os.close(fd).
112
113 @param fd open file descriptor to be closed (integer)
114 """
115 if DebugClientInstance is None:
116 DebugClientOrigClose(fd)
117
118 DebugClientInstance.close(fd)
119
120 # use our own close().
121 if 'close' in dir(os):
122 DebugClientOrigClose = os.close
123 os.close = DebugClientClose
124
125 ###############################################################################
126
127
128 def DebugClientSetRecursionLimit(limit):
129 """
130 Replacement for the standard sys.setrecursionlimit(limit).
131
132 @param limit recursion limit (integer)
133 """
134 rl = max(limit, 64)
135 setRecursionLimit(rl)
136 DebugClientOrigSetRecursionLimit(rl + 64)
137
138 # use our own setrecursionlimit().
139 if 'setrecursionlimit' in dir(sys):
140 DebugClientOrigSetRecursionLimit = sys.setrecursionlimit
141 sys.setrecursionlimit = DebugClientSetRecursionLimit
142 DebugClientSetRecursionLimit(sys.getrecursionlimit())
143
144 ###############################################################################
145
146
147 class DebugClientBase(object):
148 """
149 Class implementing the client side of the debugger.
150
151 It provides access to the Python interpeter from a debugger running in
152 another process whether or not the Qt event loop is running.
153
154 The protocol between the debugger and the client assumes that there will be
155 a single source of debugger commands and a single source of Python
156 statements. Commands and statement are always exactly one line and may be
157 interspersed.
158
159 The protocol is as follows. First the client opens a connection to the
160 debugger and then sends a series of one line commands. A command is either
161 &gt;Load&lt;, &gt;Step&lt;, &gt;StepInto&lt;, ... or a Python statement.
162 See DebugProtocol.py for a listing of valid protocol tokens.
163
164 A Python statement consists of the statement to execute, followed (in a
165 separate line) by &gt;OK?&lt;. If the statement was incomplete then the
166 response is &gt;Continue&lt;. If there was an exception then the response
167 is &gt;Exception&lt;. Otherwise the response is &gt;OK&lt;. The reason
168 for the &gt;OK?&lt; part is to provide a sentinal (ie. the responding
169 &gt;OK&lt;) after any possible output as a result of executing the command.
170
171 The client may send any other lines at any other time which should be
172 interpreted as program output.
173
174 If the debugger closes the session there is no response from the client.
175 The client may close the session at any time as a result of the script
176 being debugged closing or crashing.
177
178 <b>Note</b>: This class is meant to be subclassed by individual
179 DebugClient classes. Do not instantiate it directly.
180 """
181 clientCapabilities = DebugClientCapabilities.HasAll
182
183 def __init__(self):
184 """
185 Constructor
186 """
187 self.breakpoints = {}
188 self.redirect = True
189 self.__receiveBuffer = ""
190
191 # The next couple of members are needed for the threaded version.
192 # For this base class they contain static values for the non threaded
193 # debugger
194
195 # dictionary of all threads running
196 self.threads = {}
197
198 # the "current" thread, basically the thread we are at a
199 # breakpoint for.
200 self.currentThread = self
201
202 # special objects representing the main scripts thread and frame
203 self.mainThread = self
204 self.mainFrame = None
205 self.framenr = 0
206
207 # The context to run the debugged program in.
208 self.debugMod = imp.new_module('__main__')
209 self.debugMod.__dict__['__builtins__'] = __builtins__
210
211 # The list of complete lines to execute.
212 self.buffer = ''
213
214 # The list of regexp objects to filter variables against
215 self.globalsFilterObjects = []
216 self.localsFilterObjects = []
217
218 self._fncache = {}
219 self.dircache = []
220 self.mainProcStr = None # used for the passive mode
221 self.passive = False # used to indicate the passive mode
222 self.running = None
223 self.test = None
224 self.tracePython = False
225 self.debugging = False
226
227 self.fork_auto = False
228 self.fork_child = False
229
230 self.readstream = None
231 self.writestream = None
232 self.errorstream = None
233 self.pollingDisabled = False
234
235 self.callTraceEnabled = False
236 self.__newCallTraceEnabled = False
237
238 self.skipdirs = sys.path[:]
239
240 self.variant = 'You should not see this'
241
242 # commandline completion stuff
243 self.complete = Completer(self.debugMod.__dict__).complete
244
245 if sys.hexversion < 0x2020000:
246 self.compile_command = codeop.compile_command
247 else:
248 self.compile_command = codeop.CommandCompiler()
249
250 self.coding_re = re.compile(r"coding[:=]\s*([-\w_.]+)")
251 self.defaultCoding = 'utf-8'
252 self.__coding = self.defaultCoding
253 self.noencoding = False
254
255 def getCoding(self):
256 """
257 Public method to return the current coding.
258
259 @return codec name (string)
260 """
261 return self.__coding
262
263 def __setCoding(self, filename):
264 """
265 Private method to set the coding used by a python file.
266
267 @param filename name of the file to inspect (string)
268 """
269 if self.noencoding:
270 self.__coding = sys.getdefaultencoding()
271 else:
272 default = 'latin-1'
273 try:
274 f = open(filename, 'rb')
275 # read the first and second line
276 text = f.readline()
277 text = "%s%s" % (text, f.readline())
278 f.close()
279 except IOError:
280 self.__coding = default
281 return
282
283 for l in text.splitlines():
284 m = self.coding_re.search(l)
285 if m:
286 self.__coding = m.group(1)
287 return
288 self.__coding = default
289
290 def attachThread(self, target=None, args=None, kwargs=None, mainThread=0):
291 """
292 Public method to setup a thread for DebugClient to debug.
293
294 If mainThread is non-zero, then we are attaching to the already
295 started mainthread of the app and the rest of the args are ignored.
296
297 This is just an empty function and is overridden in the threaded
298 debugger.
299
300 @param target the start function of the target thread (i.e. the user
301 code)
302 @param args arguments to pass to target
303 @param kwargs keyword arguments to pass to target
304 @param mainThread non-zero, if we are attaching to the already
305 started mainthread of the app
306 """
307 if self.debugging:
308 sys.setprofile(self.profile)
309
310 def __dumpThreadList(self):
311 """
312 Private method to send the list of threads.
313 """
314 threadList = []
315 if self.threads and self.currentThread:
316 # indication for the threaded debugger
317 currentId = self.currentThread.get_ident()
318 for t in self.threads.values():
319 d = {}
320 d["id"] = t.get_ident()
321 d["name"] = t.get_name()
322 d["broken"] = t.isBroken()
323 threadList.append(d)
324 else:
325 currentId = -1
326 d = {}
327 d["id"] = -1
328 d["name"] = "MainThread"
329 if hasattr(self, "isBroken"):
330 d["broken"] = self.isBroken()
331 else:
332 d["broken"] = False
333 threadList.append(d)
334
335 self.sendJsonCommand("ResponseThreadList", {
336 "currentID": currentId,
337 "threadList": threadList,
338 })
339
340 def raw_input(self, prompt, echo):
341 """
342 Public method to implement raw_input() using the event loop.
343
344 @param prompt the prompt to be shown (string)
345 @param echo Flag indicating echoing of the input (boolean)
346 @return the entered string
347 """
348 self.sendJsonCommand("RequestRaw", {
349 "prompt": prompt,
350 "echo": echo,
351 })
352 self.eventLoop(True)
353 return self.rawLine
354
355 def input(self, prompt):
356 """
357 Public method to implement input() using the event loop.
358
359 @param prompt the prompt to be shown (string)
360 @return the entered string evaluated as a Python expresion
361 """
362 return eval(self.raw_input(prompt, 1))
363
364 def sessionClose(self, exit=True):
365 """
366 Public method to close the session with the debugger and optionally
367 terminate.
368
369 @param exit flag indicating to terminate (boolean)
370 """
371 try:
372 self.set_quit()
373 except Exception:
374 pass
375
376 self.debugging = False
377
378 # make sure we close down our end of the socket
379 # might be overkill as normally stdin, stdout and stderr
380 # SHOULD be closed on exit, but it does not hurt to do it here
381 self.readstream.close(True)
382 self.writestream.close(True)
383 self.errorstream.close(True)
384
385 if exit:
386 # Ok, go away.
387 sys.exit()
388
389 def handleLine(self, line):
390 """
391 Public method to handle the receipt of a complete line.
392
393 It first looks for a valid protocol token at the start of the line.
394 Thereafter it trys to execute the lines accumulated so far.
395
396 @param line the received line
397 """
398 # Remove any newline.
399 if line[-1] == '\n':
400 line = line[:-1]
401
402 ## printerr(line) ##debug
403
404 self.handleJsonCommand(line)
405
406 def handleJsonCommand(self, jsonStr):
407 """
408 Public method to handle a command serialized as a JSON string.
409
410 @param jsonStr string containing the command received from the IDE
411 @type str
412 """
413 import json
414
415 try:
416 commandDict = json.loads(jsonStr.strip())
417 except json.JSONDecodeError as err:
418 printerr(str(err))
419 return
420
421 method = commandDict["method"]
422 params = commandDict["params"]
423
424 if method == "RequestVariables":
425 self.__dumpVariables(
426 params["frameNumber"], params["scope"], params["filters"])
427
428 elif method == "RequestVariable":
429 self.__dumpVariable(
430 params["variable"], params["frameNumber"],
431 params["scope"], params["filters"])
432
433 elif method == "RequestThreadList":
434 self.__dumpThreadList()
435
436 elif method == "RequestThreadSet":
437 if params["threadID"] in self.threads:
438 self.setCurrentThread(params["threadID"])
439 self.sendJsonCommand("ResponseThreadSet", {})
440 stack = self.currentThread.getStack()
441 self.sendJsonCommand("ResponseStack", {
442 "stack": stack,
443 })
444
445 elif method == "RequestCapabilities":
446 self.sendJsonCommand("ResponseCapabilities", {
447 "capabilities": self.__clientCapabilities(),
448 "clientType": "Python3"
449 })
450
451 elif method == "RequestBanner":
452 self.sendJsonCommand("ResponseBanner", {
453 "version": "Python {0}".format(sys.version),
454 "platform": socket.gethostname(),
455 "dbgclient": self.variant,
456 })
457
458 elif method == "RequestSetFilter":
459 self.__generateFilterObjects(params["scope"], params["filter"])
460
461 elif method == "RequestCallTrace":
462 if self.debugging:
463 self.callTraceEnabled = params["enable"]
464 else:
465 self.__newCallTraceEnabled = params["enable"]
466 # remember for later
467
468 elif method == "RequestEnvironment":
469 for key, value in params["environment"].items():
470 if key.endswith("+"):
471 if key[:-1] in os.environ:
472 os.environ[key[:-1]] += value
473 else:
474 os.environ[key[:-1]] = value
475 else:
476 os.environ[key] = value
477
478 elif method == "RequestLoad":
479 self._fncache = {}
480 self.dircache = []
481 sys.argv = []
482 params["filename"] = params["filename"].encode(
483 sys.getfilesystemencoding())
484 self.__setCoding(params["filename"])
485 sys.argv.append(params["filename"])
486 sys.argv.extend(params["argv"])
487 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
488 if params["workdir"] == '':
489 os.chdir(sys.path[1])
490 else:
491 os.chdir(params["workdir"])
492
493 self.running = sys.argv[0]
494 self.mainFrame = None
495 self.debugging = True
496
497 self.fork_auto = params["autofork"]
498 self.fork_child = params["forkChild"]
499
500 self.threads.clear()
501 self.attachThread(mainThread=True)
502
503 # set the system exception handling function to ensure, that
504 # we report on all unhandled exceptions
505 sys.excepthook = self.__unhandled_exception
506 self.__interceptSignals()
507
508 # clear all old breakpoints, they'll get set after we have
509 # started
510 self.mainThread.clear_all_breaks()
511
512 self.mainThread.tracePython = params["traceInterpreter"]
513
514 # This will eventually enter a local event loop.
515 self.debugMod.__dict__['__file__'] = self.running
516 sys.modules['__main__'] = self.debugMod
517 self.callTraceEnabled = self.__newCallTraceEnabled
518 res = self.mainThread.run(
519 'execfile(' + repr(self.running) + ')',
520 self.debugMod.__dict__)
521 self.progTerminated(res)
522
523 elif method == "RequestRun":
524 sys.argv = []
525 params["filename"] = params["filename"].encode(
526 sys.getfilesystemencoding())
527 self.__setCoding(params["filename"])
528 sys.argv.append(params["filename"])
529 sys.argv.extend(params["argv"])
530 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
531 if params["workdir"] == '':
532 os.chdir(sys.path[1])
533 else:
534 os.chdir(params["workdir"])
535
536 self.running = sys.argv[0]
537 self.mainFrame = None
538 self.botframe = None
539
540 self.fork_auto = params["autofork"]
541 self.fork_child = params["forkChild"]
542
543 self.threads.clear()
544 self.attachThread(mainThread=True)
545
546 # set the system exception handling function to ensure, that
547 # we report on all unhandled exceptions
548 sys.excepthook = self.__unhandled_exception
549 self.__interceptSignals()
550
551 self.mainThread.tracePython = False
552
553 self.debugMod.__dict__['__file__'] = sys.argv[0]
554 sys.modules['__main__'] = self.debugMod
555 res = 0
556 try:
557 execfile(sys.argv[0], self.debugMod.__dict__)
558 except SystemExit as exc:
559 res = exc.code
560 atexit._run_exitfuncs()
561 self.writestream.flush()
562 self.progTerminated(res)
563
564 elif method == "RequestCoverage":
565 from coverage import coverage
566 sys.argv = []
567 params["filename"] = params["filename"].encode(
568 sys.getfilesystemencoding())
569 self.__setCoding(params["filename"])
570 sys.argv.append(params["filename"])
571 sys.argv.extend(params["argv"])
572 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
573 if params["workdir"] == '':
574 os.chdir(sys.path[1])
575 else:
576 os.chdir(params["workdir"])
577
578 # set the system exception handling function to ensure, that
579 # we report on all unhandled exceptions
580 sys.excepthook = self.__unhandled_exception
581 self.__interceptSignals()
582
583 # generate a coverage object
584 self.cover = coverage(
585 auto_data=True,
586 data_file="%s.coverage" % os.path.splitext(sys.argv[0])[0])
587
588 if params["erase"]:
589 self.cover.erase()
590 sys.modules['__main__'] = self.debugMod
591 self.debugMod.__dict__['__file__'] = sys.argv[0]
592 self.running = sys.argv[0]
593 res = 0
594 self.cover.start()
595 try:
596 execfile(sys.argv[0], self.debugMod.__dict__)
597 except SystemExit as exc:
598 res = exc.code
599 atexit._run_exitfuncs()
600 self.cover.stop()
601 self.cover.save()
602 self.writestream.flush()
603 self.progTerminated(res)
604
605 elif method == "RequestProfile":
606 sys.setprofile(None)
607 import PyProfile
608 sys.argv = []
609 params["filename"] = params["filename"].encode(
610 sys.getfilesystemencoding())
611 self.__setCoding(params["filename"])
612 sys.argv.append(params["filename"])
613 sys.argv.extend(params["argv"])
614 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
615 if params["workdir"] == '':
616 os.chdir(sys.path[1])
617 else:
618 os.chdir(params["workdir"])
619
620 # set the system exception handling function to ensure, that
621 # we report on all unhandled exceptions
622 sys.excepthook = self.__unhandled_exception
623 self.__interceptSignals()
624
625 # generate a profile object
626 self.prof = PyProfile.PyProfile(sys.argv[0])
627
628 if params["erase"]:
629 self.prof.erase()
630 self.debugMod.__dict__['__file__'] = sys.argv[0]
631 sys.modules['__main__'] = self.debugMod
632 self.running = sys.argv[0]
633 res = 0
634 try:
635 self.prof.run('execfile(%r)' % sys.argv[0])
636 except SystemExit as exc:
637 res = exc.code
638 atexit._run_exitfuncs()
639 self.prof.save()
640 self.writestream.flush()
641 self.progTerminated(res)
642
643 elif method == "ExecuteStatement":
644 if self.buffer:
645 self.buffer = self.buffer + '\n' + params["statement"]
646 else:
647 self.buffer = params["statement"]
648
649 try:
650 code = self.compile_command(self.buffer, self.readstream.name)
651 except (OverflowError, SyntaxError, ValueError):
652 # Report the exception
653 sys.last_type, sys.last_value, sys.last_traceback = \
654 sys.exc_info()
655 self.sendJsonCommand("ClientOutput", {
656 "text": "".join(traceback.format_exception_only(
657 sys.last_type, sys.last_value))
658 })
659 self.buffer = ''
660 else:
661 if code is None:
662 self.sendJsonCommand("ResponseContinue", {})
663 return
664 else:
665 self.buffer = ''
666
667 try:
668 if self.running is None:
669 exec code in self.debugMod.__dict__
670 else:
671 if self.currentThread is None:
672 # program has terminated
673 self.running = None
674 _globals = self.debugMod.__dict__
675 _locals = _globals
676 else:
677 cf = self.currentThread.getCurrentFrame()
678 # program has terminated
679 if cf is None:
680 self.running = None
681 _globals = self.debugMod.__dict__
682 _locals = _globals
683 else:
684 frmnr = self.framenr
685 while cf is not None and frmnr > 0:
686 cf = cf.f_back
687 frmnr -= 1
688 _globals = cf.f_globals
689 _locals = \
690 self.currentThread.getFrameLocals(
691 self.framenr)
692 # reset sys.stdout to our redirector
693 # (unconditionally)
694 if "sys" in _globals:
695 __stdout = _globals["sys"].stdout
696 _globals["sys"].stdout = self.writestream
697 exec code in _globals, _locals
698 _globals["sys"].stdout = __stdout
699 elif "sys" in _locals:
700 __stdout = _locals["sys"].stdout
701 _locals["sys"].stdout = self.writestream
702 exec code in _globals, _locals
703 _locals["sys"].stdout = __stdout
704 else:
705 exec code in _globals, _locals
706
707 self.currentThread.storeFrameLocals(self.framenr)
708 except SystemExit, exc:
709 self.progTerminated(exc.code)
710 except Exception:
711 # Report the exception and the traceback
712 tlist = []
713 try:
714 exc_type, exc_value, exc_tb = sys.exc_info()
715 sys.last_type = exc_type
716 sys.last_value = exc_value
717 sys.last_traceback = exc_tb
718 tblist = traceback.extract_tb(exc_tb)
719 del tblist[:1]
720 tlist = traceback.format_list(tblist)
721 if tlist:
722 tlist.insert(
723 0, "Traceback (innermost last):\n")
724 tlist.extend(traceback.format_exception_only(
725 exc_type, exc_value))
726 finally:
727 tblist = exc_tb = None
728
729 self.sendJsonCommand("ClientOutput", {
730 "text": "".join(tlist)
731 })
732
733 self.sendJsonCommand("ResponseOK", {})
734
735 elif method == "RequestStep":
736 self.currentThread.step(True)
737 self.eventExit = True
738
739 elif method == "RequestStepOver":
740 self.currentThread.step(False)
741 self.eventExit = True
742
743 elif method == "RequestStepOut":
744 self.currentThread.stepOut()
745 self.eventExit = True
746
747 elif method == "RequestStepQuit":
748 if self.passive:
749 self.progTerminated(42)
750 else:
751 self.set_quit()
752 self.eventExit = True
753
754 elif method == "RequestContinue":
755 self.currentThread.go(params["special"])
756 self.eventExit = True
757
758 elif method == "RawInput":
759 # If we are handling raw mode input then break out of the current
760 # event loop.
761 self.rawLine = params["input"]
762 self.eventExit = True
763
764 elif method == "RequestBreakpoint":
765 params["filename"] = params["filename"].encode(
766 sys.getfilesystemencoding())
767 if params["setBreakpoint"]:
768 if params["condition"] in ['None', '']:
769 params["condition"] = None
770 elif params["condition"] is not None:
771 try:
772 compile(params["condition"], '<string>', 'eval')
773 except SyntaxError:
774 self.sendJsonCommand("ResponseBPConditionError", {
775 "filename": params["filename"],
776 "line": params["line"],
777 })
778 return
779 self.mainThread.set_break(
780 params["filename"], params["line"], params["temporary"],
781 params["condition"])
782 else:
783 self.mainThread.clear_break(params["filename"], params["line"])
784
785 elif method == "RequestBreakpointEnable":
786 params["filename"] = params["filename"].encode(
787 sys.getfilesystemencoding())
788 bp = self.mainThread.get_break(params["filename"], params["line"])
789 if bp is not None:
790 if params["enable"]:
791 bp.enable()
792 else:
793 bp.disable()
794
795
796 elif method == "RequestBreakpointIgnore":
797 params["filename"] = params["filename"].encode(
798 sys.getfilesystemencoding())
799 bp = self.mainThread.get_break(params["filename"], params["line"])
800 if bp is not None:
801 bp.ignore = params["count"]
802
803 elif method == "RequestWatch":
804 if params["setWatch"]:
805 if not params["condition"].endswith(
806 ('??created??', '??changed??')):
807 try:
808 compile(params["condition"], '<string>', 'eval')
809 except SyntaxError:
810 self.sendJsonCommand("ResponseWatchConditionError", {
811 "condition": params["condition"],
812 })
813 return
814 self.mainThread.set_watch(
815 params["condition"], params["temporary"])
816 else:
817 self.mainThread.clear_watch(params["condition"])
818
819 elif method == "RequestWatchEnable":
820 wp = self.mainThread.get_watch(params["condition"])
821 if wp is not None:
822 if params["enable"]:
823 wp.enable()
824 else:
825 wp.disable()
826
827 elif method == "RequestWatchIgnore":
828 wp = self.mainThread.get_watch(params["condition"])
829 if wp is not None:
830 wp.ignore = params["count"]
831
832 elif method == "RequestShutdown":
833 self.sessionClose()
834
835 elif method == "RequestCompletion":
836 self.__completionList(params["text"])
837
838 elif method == "RequestUTPrepare":
839 params["filename"] = params["filename"].encode(
840 sys.getfilesystemencoding())
841 sys.path.insert(
842 0, os.path.dirname(os.path.abspath(params["filename"])))
843 os.chdir(sys.path[0])
844
845 # set the system exception handling function to ensure, that
846 # we report on all unhandled exceptions
847 sys.excepthook = self.__unhandled_exception
848 self.__interceptSignals()
849
850 try:
851 import unittest
852 utModule = __import__(params["testname"])
853 try:
854 if params["failed"]:
855 self.test = unittest.defaultTestLoader\
856 .loadTestsFromNames(params["failed"], utModule)
857 else:
858 self.test = unittest.defaultTestLoader\
859 .loadTestsFromName(params["testfunctionname"],
860 utModule)
861 except AttributeError:
862 self.test = unittest.defaultTestLoader\
863 .loadTestsFromModule(utModule)
864 except Exception:
865 exc_type, exc_value, exc_tb = sys.exc_info()
866 self.sendJsonCommand("ResponseUTPrepared", {
867 "count": 0,
868 "exception": exc_type.__name__,
869 "message": str(exc_value),
870 })
871 return
872
873 # generate a coverage object
874 if params["coverage"]:
875 from coverage import coverage
876 self.cover = coverage(
877 auto_data=True,
878 data_file="%s.coverage" % \
879 os.path.splitext(params["coveragefile"])[0])
880 if params["coverageerase"]:
881 self.cover.erase()
882 else:
883 self.cover = None
884
885 self.sendJsonCommand("ResponseUTPrepared", {
886 "count": self.test.countTestCases(),
887 "exception": "",
888 "message": "",
889 })
890
891 elif method == "RequestUTRun":
892 from DCTestResult import DCTestResult
893 self.testResult = DCTestResult(self)
894 if self.cover:
895 self.cover.start()
896 self.test.run(self.testResult)
897 if self.cover:
898 self.cover.stop()
899 self.cover.save()
900 self.sendJsonCommand("ResponseUTFinished", {})
901
902 elif method == "RequestUTStop":
903 self.testResult.stop()
904
905 elif method == "ResponseForkTo":
906 # this results from a separate event loop
907 self.fork_child = (params["target"] == 'child')
908 self.eventExit = True
909
910 def sendJsonCommand(self, method, params):
911 """
912 Public method to send a single command or response to the IDE.
913
914 @param method command or response command name to be sent
915 @type str
916 @param params dictionary of named parameters for the command or
917 response
918 @type dict
919 """
920 cmd = prepareJsonCommand(method, params)
921
922 self.writestream.write_p(cmd)
923 self.writestream.flush()
924
925 def sendClearTemporaryBreakpoint(self, filename, lineno):
926 """
927 Public method to signal the deletion of a temporary breakpoint.
928
929 @param filename name of the file the bp belongs to
930 @type str
931 @param lineno linenumber of the bp
932 @type int
933 """
934 self.sendJsonCommand("ResponseClearBreakpoint", {
935 "filename": filename,
936 "line": lineno
937 })
938
939 def sendClearTemporaryWatch(self, condition):
940 """
941 Public method to signal the deletion of a temporary watch expression.
942
943 @param condition condition of the watch expression to be cleared
944 @type str
945 """
946 self.sendJsonCommand("ResponseClearWatch", {
947 "condition": condition,
948 })
949
950 def sendResponseLine(self, stack):
951 """
952 Public method to send the current call stack.
953
954 @param stack call stack
955 @type list
956 """
957 self.sendJsonCommand("ResponseLine", {
958 "stack": stack,
959 })
960
961 def sendCallTrace(self, event, fromStr, toStr):
962 """
963 Public method to send a call trace entry.
964
965 @param event trace event (call or return)
966 @type str
967 @param fromStr pre-formatted origin info
968 @type str
969 @param toStr pre-formatted target info
970 @type str
971 """
972 self.sendJsonCommand("CallTrace", {
973 "event": event[0],
974 "from": fromStr,
975 "to": toStr,
976 })
977
978 def sendException(self, exceptionType, exceptionMessage, stack):
979 """
980 Public method to send information for an exception.
981
982 @param exceptionType type of exception raised
983 @type str
984 @param exceptionMessage message of the exception
985 @type str
986 @param stack stack trace information
987 @type list
988 """
989 self.sendJsonCommand("ResponseException", {
990 "type": exceptionType,
991 "message": exceptionMessage,
992 "stack": stack,
993 })
994
995 def sendSyntaxError(self, message, filename, lineno, charno):
996 """
997 Public method to send information for a syntax error.
998
999 @param message syntax error message
1000 @type str
1001 @param filename name of the faulty file
1002 @type str
1003 @param lineno line number info
1004 @type int
1005 @param charno character number info
1006 @tyoe int
1007 """
1008 self.sendJsonCommand("ResponseSyntax", {
1009 "message": message,
1010 "filename": filename,
1011 "linenumber": lineno,
1012 "characternumber": charno,
1013 })
1014
1015 def sendPassiveStartup(self, filename, exceptions):
1016 """
1017 Public method to send the passive start information.
1018
1019 @param filename name of the script
1020 @type str
1021 @param exceptions flag to enable exception reporting of the IDE
1022 @type bool
1023 """
1024 self.sendJsonCommand("PassiveStartup", {
1025 "filename": filename,
1026 "exceptions": exceptions,
1027 })
1028
1029 def __clientCapabilities(self):
1030 """
1031 Private method to determine the clients capabilities.
1032
1033 @return client capabilities (integer)
1034 """
1035 try:
1036 import PyProfile # __IGNORE_WARNING__
1037 try:
1038 del sys.modules['PyProfile']
1039 except KeyError:
1040 pass
1041 return self.clientCapabilities
1042 except ImportError:
1043 return (
1044 self.clientCapabilities & ~DebugClientCapabilities.HasProfiler)
1045
1046 def readReady(self, stream):
1047 """
1048 Public method called when there is data ready to be read.
1049
1050 @param stream file like object that has data to be written
1051 """
1052 try:
1053 got = stream.readline_p()
1054 except Exception:
1055 return
1056
1057 if len(got) == 0:
1058 self.sessionClose()
1059 return
1060
1061 self.__receiveBuffer = self.__receiveBuffer + got
1062
1063 # Call handleLine for the line if it is complete.
1064 eol = self.__receiveBuffer.find('\n')
1065 while eol >= 0:
1066 line = self.__receiveBuffer[:eol + 1]
1067 self.__receiveBuffer = self.__receiveBuffer[eol + 1:]
1068 self.handleLine(line)
1069 eol = self.__receiveBuffer.find('\n')
1070
1071 def writeReady(self, stream):
1072 """
1073 Public method called when we are ready to write data.
1074
1075 @param stream file like object that has data to be written
1076 """
1077 stream.write_p("")
1078 stream.flush()
1079
1080 def __interact(self):
1081 """
1082 Private method to interact with the debugger.
1083 """
1084 global DebugClientInstance
1085
1086 DebugClientInstance = self
1087 self.__receiveBuffer = ""
1088
1089 if not self.passive:
1090 # At this point simulate an event loop.
1091 self.eventLoop()
1092
1093 def eventLoop(self, disablePolling=False):
1094 """
1095 Public method implementing our event loop.
1096
1097 @param disablePolling flag indicating to enter an event loop with
1098 polling disabled (boolean)
1099 """
1100 self.eventExit = None
1101 self.pollingDisabled = disablePolling
1102
1103 while self.eventExit is None:
1104 wrdy = []
1105
1106 if self.writestream.nWriteErrors > self.writestream.maxtries:
1107 break
1108
1109 if AsyncPendingWrite(self.writestream):
1110 wrdy.append(self.writestream)
1111
1112 if AsyncPendingWrite(self.errorstream):
1113 wrdy.append(self.errorstream)
1114
1115 try:
1116 rrdy, wrdy, xrdy = select.select([self.readstream], wrdy, [])
1117 except (select.error, KeyboardInterrupt, socket.error):
1118 # just carry on
1119 continue
1120
1121 if self.readstream in rrdy:
1122 self.readReady(self.readstream)
1123
1124 if self.writestream in wrdy:
1125 self.writeReady(self.writestream)
1126
1127 if self.errorstream in wrdy:
1128 self.writeReady(self.errorstream)
1129
1130 self.eventExit = None
1131 self.pollingDisabled = False
1132
1133 def eventPoll(self):
1134 """
1135 Public method to poll for events like 'set break point'.
1136 """
1137 if self.pollingDisabled:
1138 return
1139
1140 # the choice of a ~0.5 second poll interval is arbitrary.
1141 lasteventpolltime = getattr(self, 'lasteventpolltime', time.time())
1142 now = time.time()
1143 if now - lasteventpolltime < 0.5:
1144 self.lasteventpolltime = lasteventpolltime
1145 return
1146 else:
1147 self.lasteventpolltime = now
1148
1149 wrdy = []
1150 if AsyncPendingWrite(self.writestream):
1151 wrdy.append(self.writestream)
1152
1153 if AsyncPendingWrite(self.errorstream):
1154 wrdy.append(self.errorstream)
1155
1156 # immediate return if nothing is ready.
1157 try:
1158 rrdy, wrdy, xrdy = select.select([self.readstream], wrdy, [], 0)
1159 except (select.error, KeyboardInterrupt, socket.error):
1160 return
1161
1162 if self.readstream in rrdy:
1163 self.readReady(self.readstream)
1164
1165 if self.writestream in wrdy:
1166 self.writeReady(self.writestream)
1167
1168 if self.errorstream in wrdy:
1169 self.writeReady(self.errorstream)
1170
1171 def connectDebugger(self, port, remoteAddress=None, redirect=1):
1172 """
1173 Public method to establish a session with the debugger.
1174
1175 It opens a network connection to the debugger, connects it to stdin,
1176 stdout and stderr and saves these file objects in case the application
1177 being debugged redirects them itself.
1178
1179 @param port the port number to connect to (int)
1180 @param remoteAddress the network address of the debug server host
1181 (string)
1182 @param redirect flag indicating redirection of stdin, stdout and
1183 stderr (boolean)
1184 """
1185 if remoteAddress is None:
1186 remoteAddress = "127.0.0.1"
1187 elif "@@i" in remoteAddress:
1188 remoteAddress = remoteAddress.split("@@i")[0]
1189 sock = socket.create_connection((remoteAddress, port))
1190
1191 self.readstream = AsyncFile(sock, sys.stdin.mode, sys.stdin.name)
1192 self.writestream = AsyncFile(sock, sys.stdout.mode, sys.stdout.name)
1193 self.errorstream = AsyncFile(sock, sys.stderr.mode, sys.stderr.name)
1194
1195 if redirect:
1196 sys.stdin = self.readstream
1197 sys.stdout = self.writestream
1198 sys.stderr = self.errorstream
1199 self.redirect = redirect
1200
1201 # attach to the main thread here
1202 self.attachThread(mainThread=1)
1203
1204 def __unhandled_exception(self, exctype, excval, exctb):
1205 """
1206 Private method called to report an uncaught exception.
1207
1208 @param exctype the type of the exception
1209 @param excval data about the exception
1210 @param exctb traceback for the exception
1211 """
1212 self.mainThread.user_exception(None, (exctype, excval, exctb), True)
1213
1214 def __interceptSignals(self):
1215 """
1216 Private method to intercept common signals.
1217 """
1218 for signum in [
1219 signal.SIGABRT, # abnormal termination
1220 signal.SIGFPE, # floating point exception
1221 signal.SIGILL, # illegal instruction
1222 signal.SIGSEGV, # segmentation violation
1223 ]:
1224 signal.signal(signum, self.__signalHandler)
1225
1226 def __signalHandler(self, signalNumber, stackFrame):
1227 """
1228 Private method to handle signals.
1229
1230 @param signalNumber number of the signal to be handled
1231 @type int
1232 @param stackFrame current stack frame
1233 @type frame object
1234 """
1235 if signalNumber == signal.SIGABRT:
1236 message = "Abnormal Termination"
1237 elif signalNumber == signal.SIGFPE:
1238 message = "Floating Point Exception"
1239 elif signalNumber == signal.SIGILL:
1240 message = "Illegal Instruction"
1241 elif signalNumber == signal.SIGSEGV:
1242 message = "Segmentation Violation"
1243 else:
1244 message = "Unknown Signal '%d'" % signalNumber
1245
1246 filename = self.absPath(stackFrame)
1247
1248 linenr = stackFrame.f_lineno
1249 ffunc = stackFrame.f_code.co_name
1250
1251 if ffunc == '?':
1252 ffunc = ''
1253
1254 if ffunc and not ffunc.startswith("<"):
1255 argInfo = inspect.getargvalues(stackFrame)
1256 try:
1257 fargs = inspect.formatargvalues(
1258 argInfo.args, argInfo.varargs,
1259 argInfo.keywords, argInfo.locals)
1260 except Exception:
1261 fargs = ""
1262 else:
1263 fargs = ""
1264
1265 self.sendJsonCommand("ResponseSignal", {
1266 "message": message,
1267 "filename": filename,
1268 "linenumber": linenr,
1269 "function": ffunc,
1270 "arguments": fargs,
1271 })
1272
1273 def absPath(self, fn):
1274 """
1275 Public method to convert a filename to an absolute name.
1276
1277 sys.path is used as a set of possible prefixes. The name stays
1278 relative if a file could not be found.
1279
1280 @param fn filename (string)
1281 @return the converted filename (string)
1282 """
1283 if os.path.isabs(fn):
1284 return fn
1285
1286 # Check the cache.
1287 if fn in self._fncache:
1288 return self._fncache[fn]
1289
1290 # Search sys.path.
1291 for p in sys.path:
1292 afn = os.path.abspath(os.path.join(p, fn))
1293 nafn = os.path.normcase(afn)
1294
1295 if os.path.exists(nafn):
1296 self._fncache[fn] = afn
1297 d = os.path.dirname(afn)
1298 if (d not in sys.path) and (d not in self.dircache):
1299 self.dircache.append(d)
1300 return afn
1301
1302 # Search the additional directory cache
1303 for p in self.dircache:
1304 afn = os.path.abspath(os.path.join(p, fn))
1305 nafn = os.path.normcase(afn)
1306
1307 if os.path.exists(nafn):
1308 self._fncache[fn] = afn
1309 return afn
1310
1311 # Nothing found.
1312 return fn
1313
1314 def shouldSkip(self, fn):
1315 """
1316 Public method to check if a file should be skipped.
1317
1318 @param fn filename to be checked
1319 @return non-zero if fn represents a file we are 'skipping',
1320 zero otherwise.
1321 """
1322 if self.mainThread.tracePython: # trace into Python library
1323 return False
1324
1325 # Eliminate anything that is part of the Python installation.
1326 afn = self.absPath(fn)
1327 for d in self.skipdirs:
1328 if afn.startswith(d):
1329 return True
1330
1331 # special treatment for paths containing site-packages or dist-packages
1332 for part in ["site-packages", "dist-packages"]:
1333 if part in afn:
1334 return True
1335
1336 return False
1337
1338 def getRunning(self):
1339 """
1340 Public method to return the main script we are currently running.
1341
1342 @return flag indicating a running debug session (boolean)
1343 """
1344 return self.running
1345
1346 def progTerminated(self, status, message=""):
1347 """
1348 Public method to tell the debugger that the program has terminated.
1349
1350 @param status return status
1351 @type int
1352 @param message status message
1353 @type str
1354 """
1355 if status is None:
1356 status = 0
1357 elif not isinstance(status, int):
1358 message = str(status)
1359 status = 1
1360
1361 if self.running:
1362 self.set_quit()
1363 self.running = None
1364 self.sendJsonCommand("ResponseExit", {
1365 "status": status,
1366 "message": message,
1367 })
1368
1369 # reset coding
1370 self.__coding = self.defaultCoding
1371
1372 def __dumpVariables(self, frmnr, scope, filter):
1373 """
1374 Private method to return the variables of a frame to the debug server.
1375
1376 @param frmnr distance of frame reported on. 0 is the current frame
1377 (int)
1378 @param scope 1 to report global variables, 0 for local variables (int)
1379 @param filter the indices of variable types to be filtered (list of
1380 int)
1381 """
1382 if self.currentThread is None:
1383 return
1384
1385 if scope == 0:
1386 self.framenr = frmnr
1387
1388 f = self.currentThread.getCurrentFrame()
1389
1390 while f is not None and frmnr > 0:
1391 f = f.f_back
1392 frmnr -= 1
1393
1394 if f is None:
1395 if scope:
1396 dict = self.debugMod.__dict__
1397 else:
1398 scope = -1
1399 elif scope:
1400 dict = f.f_globals
1401 elif f.f_globals is f.f_locals:
1402 scope = -1
1403 else:
1404 dict = f.f_locals
1405
1406 varlist = []
1407
1408 if scope != -1:
1409 keylist = dict.keys()
1410
1411 vlist = self.__formatVariablesList(keylist, dict, scope, filter)
1412 varlist.extend(vlist)
1413
1414 self.sendJsonCommand("ResponseVariables", {
1415 "scope": scope,
1416 "variables": varlist,
1417 })
1418
1419 def __dumpVariable(self, var, frmnr, scope, filter):
1420 """
1421 Private method to return the variables of a frame to the debug server.
1422
1423 @param var list encoded name of the requested variable
1424 (list of strings)
1425 @param frmnr distance of frame reported on. 0 is the current frame
1426 (int)
1427 @param scope 1 to report global variables, 0 for local variables (int)
1428 @param filter the indices of variable types to be filtered
1429 (list of int)
1430 """
1431 if self.currentThread is None:
1432 return
1433
1434 f = self.currentThread.getCurrentFrame()
1435
1436 while f is not None and frmnr > 0:
1437 f = f.f_back
1438 frmnr -= 1
1439
1440 if f is None:
1441 if scope:
1442 dict = self.debugMod.__dict__
1443 else:
1444 scope = -1
1445 elif scope:
1446 dict = f.f_globals
1447 elif f.f_globals is f.f_locals:
1448 scope = -1
1449 else:
1450 dict = f.f_locals
1451
1452 varlist = []
1453
1454 if scope != -1:
1455 # search the correct dictionary
1456 i = 0
1457 rvar = var[:]
1458 dictkeys = None
1459 obj = None
1460 isDict = False
1461 formatSequences = False
1462 access = ""
1463 oaccess = ""
1464 odict = dict
1465
1466 qtVariable = False
1467 qvar = None
1468 qvtype = ""
1469
1470 while i < len(var):
1471 if len(dict):
1472 udict = dict
1473 ndict = {}
1474 # this has to be in line with VariablesViewer.indicators
1475 if var[i][-2:] in ["[]", "()", "{}"]: # __IGNORE_WARNING__
1476 if i + 1 == len(var):
1477 if var[i][:-2] == '...':
1478 dictkeys = [var[i - 1]]
1479 else:
1480 dictkeys = [var[i][:-2]]
1481 formatSequences = True
1482 if not access and not oaccess:
1483 if var[i][:-2] == '...':
1484 access = '["%s"]' % var[i - 1]
1485 dict = odict
1486 else:
1487 access = '["%s"]' % var[i][:-2]
1488 else:
1489 if var[i][:-2] == '...':
1490 if oaccess:
1491 access = oaccess
1492 else:
1493 access = '%s[%s]' % (access, var[i - 1])
1494 dict = odict
1495 else:
1496 if oaccess:
1497 access = '%s[%s]' % (oaccess, var[i][:-2])
1498 oaccess = ''
1499 else:
1500 access = '%s[%s]' % (access, var[i][:-2])
1501 if var[i][-2:] == "{}": # __IGNORE_WARNING__
1502 isDict = True
1503 break
1504 else:
1505 if not access:
1506 if var[i][:-2] == '...':
1507 access = '["%s"]' % var[i - 1]
1508 dict = odict
1509 else:
1510 access = '["%s"]' % var[i][:-2]
1511 else:
1512 if var[i][:-2] == '...':
1513 access = '%s[%s]' % (access, var[i - 1])
1514 dict = odict
1515 else:
1516 if oaccess:
1517 access = '%s[%s]' % (oaccess, var[i][:-2])
1518 oaccess = ''
1519 else:
1520 access = '%s[%s]' % (access, var[i][:-2])
1521 else:
1522 if access:
1523 if oaccess:
1524 access = '%s[%s]' % (oaccess, var[i])
1525 else:
1526 access = '%s[%s]' % (access, var[i])
1527 if var[i - 1][:-2] == '...':
1528 oaccess = access
1529 else:
1530 oaccess = ''
1531 try:
1532 exec 'mdict = dict%s.__dict__' % access
1533 ndict.update(mdict) # __IGNORE_WARNING__
1534 exec 'obj = dict%s' % access
1535 if "PyQt4." in str(type(obj)) or \
1536 "PyQt5." in str(type(obj)):
1537 qtVariable = True
1538 qvar = obj
1539 qvtype = ("%s" % type(qvar))[1:-1]\
1540 .split()[1][1:-1]
1541 except Exception:
1542 pass
1543 try:
1544 exec 'mcdict = dict%s.__class__.__dict__' % access
1545 ndict.update(mcdict) # __IGNORE_WARNING__
1546 if mdict and "sipThis" not in mdict.keys(): # __IGNORE_WARNING__
1547 del rvar[0:2]
1548 access = ""
1549 except Exception:
1550 pass
1551 try:
1552 cdict = {}
1553 exec 'slv = dict%s.__slots__' % access
1554 for v in slv: # __IGNORE_WARNING__
1555 try:
1556 exec 'cdict[v] = dict%s.%s' % (access, v)
1557 except Exception:
1558 pass
1559 ndict.update(cdict)
1560 exec 'obj = dict%s' % access
1561 access = ""
1562 if "PyQt4." in str(type(obj)) or \
1563 "PyQt5." in str(type(obj)):
1564 qtVariable = True
1565 qvar = obj
1566 qvtype = ("%s" % type(qvar))[1:-1]\
1567 .split()[1][1:-1]
1568 except Exception:
1569 pass
1570 else:
1571 try:
1572 ndict.update(dict[var[i]].__dict__)
1573 ndict.update(dict[var[i]].__class__.__dict__)
1574 del rvar[0]
1575 obj = dict[var[i]]
1576 if "PyQt4." in str(type(obj)) or \
1577 "PyQt5." in str(type(obj)):
1578 qtVariable = True
1579 qvar = obj
1580 qvtype = ("%s" % type(qvar))[1:-1]\
1581 .split()[1][1:-1]
1582 except Exception:
1583 pass
1584 try:
1585 cdict = {}
1586 slv = dict[var[i]].__slots__
1587 for v in slv:
1588 try:
1589 exec 'cdict[v] = dict[var[i]].%s' % v
1590 except Exception:
1591 pass
1592 ndict.update(cdict)
1593 obj = dict[var[i]]
1594 if "PyQt4." in str(type(obj)) or \
1595 "PyQt5." in str(type(obj)):
1596 qtVariable = True
1597 qvar = obj
1598 qvtype = ("%s" % type(qvar))[1:-1]\
1599 .split()[1][1:-1]
1600 except Exception:
1601 pass
1602 odict = dict
1603 dict = ndict
1604 i += 1
1605
1606 if qtVariable:
1607 vlist = self.__formatQtVariable(qvar, qvtype)
1608 elif ("sipThis" in dict.keys() and len(dict) == 1) or \
1609 (len(dict) == 0 and len(udict) > 0):
1610 if access:
1611 exec 'qvar = udict%s' % access
1612 # this has to be in line with VariablesViewer.indicators
1613 elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]: # __IGNORE_WARNING__
1614 exec 'qvar = udict["%s"][%s]' % (rvar[0][:-2], rvar[1])
1615 else:
1616 qvar = udict[var[-1]]
1617 qvtype = ("%s" % type(qvar))[1:-1].split()[1][1:-1]
1618 if qvtype.startswith(("PyQt4", "PyQt5")):
1619 vlist = self.__formatQtVariable(qvar, qvtype)
1620 else:
1621 vlist = []
1622 else:
1623 qtVariable = False
1624 if len(dict) == 0 and len(udict) > 0:
1625 if access:
1626 exec 'qvar = udict%s' % access
1627 # this has to be in line with VariablesViewer.indicators
1628 elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]: # __IGNORE_WARNING__
1629 exec 'qvar = udict["%s"][%s]' % (rvar[0][:-2], rvar[1])
1630 else:
1631 qvar = udict[var[-1]]
1632 qvtype = ("%s" % type(qvar))[1:-1].split()[1][1:-1]
1633 if qvtype.startswith(("PyQt4", "PyQt5")):
1634 qtVariable = True
1635
1636 if qtVariable:
1637 vlist = self.__formatQtVariable(qvar, qvtype)
1638 else:
1639 # format the dictionary found
1640 if dictkeys is None:
1641 dictkeys = dict.keys()
1642 else:
1643 # treatment for sequences and dictionaries
1644 if access:
1645 exec "dict = dict%s" % access
1646 else:
1647 dict = dict[dictkeys[0]]
1648 if isDict:
1649 dictkeys = dict.keys()
1650 else:
1651 dictkeys = range(len(dict))
1652 vlist = self.__formatVariablesList(
1653 dictkeys, dict, scope, filter, formatSequences)
1654 varlist.extend(vlist)
1655
1656 if obj is not None and not formatSequences:
1657 try:
1658 if unicode(repr(obj)).startswith('{'):
1659 varlist.append(('...', 'dict', "%d" % len(obj.keys())))
1660 elif unicode(repr(obj)).startswith('['):
1661 varlist.append(('...', 'list', "%d" % len(obj)))
1662 elif unicode(repr(obj)).startswith('('):
1663 varlist.append(('...', 'tuple', "%d" % len(obj)))
1664 except Exception:
1665 pass
1666
1667 self.sendJsonCommand("ResponseVariable", {
1668 "scope": scope,
1669 "variable": var,
1670 "variables": varlist,
1671 })
1672
1673 def __formatQtVariable(self, value, vtype):
1674 """
1675 Private method to produce a formated output of a simple Qt4/Qt5 type.
1676
1677 @param value variable to be formated
1678 @param vtype type of the variable to be formatted (string)
1679 @return A tuple consisting of a list of formatted variables. Each
1680 variable entry is a tuple of three elements, the variable name,
1681 its type and value.
1682 """
1683 qttype = vtype.split('.')[-1]
1684 varlist = []
1685 if qttype == 'QChar':
1686 varlist.append(("", "QChar", "%s" % unichr(value.unicode())))
1687 varlist.append(("", "int", "%d" % value.unicode()))
1688 elif qttype == 'QByteArray':
1689 varlist.append(("hex", "QByteArray", "%s" % value.toHex()))
1690 varlist.append(("base64", "QByteArray", "%s" % value.toBase64()))
1691 varlist.append(("percent encoding", "QByteArray",
1692 "%s" % value.toPercentEncoding()))
1693 elif qttype == 'QString':
1694 varlist.append(("", "QString", "%s" % value))
1695 elif qttype == 'QStringList':
1696 for i in range(value.count()):
1697 varlist.append(("%d" % i, "QString", "%s" % value[i]))
1698 elif qttype == 'QPoint':
1699 varlist.append(("x", "int", "%d" % value.x()))
1700 varlist.append(("y", "int", "%d" % value.y()))
1701 elif qttype == 'QPointF':
1702 varlist.append(("x", "float", "%g" % value.x()))
1703 varlist.append(("y", "float", "%g" % value.y()))
1704 elif qttype == 'QRect':
1705 varlist.append(("x", "int", "%d" % value.x()))
1706 varlist.append(("y", "int", "%d" % value.y()))
1707 varlist.append(("width", "int", "%d" % value.width()))
1708 varlist.append(("height", "int", "%d" % value.height()))
1709 elif qttype == 'QRectF':
1710 varlist.append(("x", "float", "%g" % value.x()))
1711 varlist.append(("y", "float", "%g" % value.y()))
1712 varlist.append(("width", "float", "%g" % value.width()))
1713 varlist.append(("height", "float", "%g" % value.height()))
1714 elif qttype == 'QSize':
1715 varlist.append(("width", "int", "%d" % value.width()))
1716 varlist.append(("height", "int", "%d" % value.height()))
1717 elif qttype == 'QSizeF':
1718 varlist.append(("width", "float", "%g" % value.width()))
1719 varlist.append(("height", "float", "%g" % value.height()))
1720 elif qttype == 'QColor':
1721 varlist.append(("name", "str", "%s" % value.name()))
1722 r, g, b, a = value.getRgb()
1723 varlist.append(("rgba", "int", "%d, %d, %d, %d" % (r, g, b, a)))
1724 h, s, v, a = value.getHsv()
1725 varlist.append(("hsva", "int", "%d, %d, %d, %d" % (h, s, v, a)))
1726 c, m, y, k, a = value.getCmyk()
1727 varlist.append(
1728 ("cmyka", "int", "%d, %d, %d, %d, %d" % (c, m, y, k, a)))
1729 elif qttype == 'QDate':
1730 varlist.append(("", "QDate", "%s" % value.toString()))
1731 elif qttype == 'QTime':
1732 varlist.append(("", "QTime", "%s" % value.toString()))
1733 elif qttype == 'QDateTime':
1734 varlist.append(("", "QDateTime", "%s" % value.toString()))
1735 elif qttype == 'QDir':
1736 varlist.append(("path", "str", "%s" % value.path()))
1737 varlist.append(
1738 ("absolutePath", "str", "%s" % value.absolutePath()))
1739 varlist.append(
1740 ("canonicalPath", "str", "%s" % value.canonicalPath()))
1741 elif qttype == 'QFile':
1742 varlist.append(("fileName", "str", "%s" % value.fileName()))
1743 elif qttype == 'QFont':
1744 varlist.append(("family", "str", "%s" % value.family()))
1745 varlist.append(("pointSize", "int", "%d" % value.pointSize()))
1746 varlist.append(("weight", "int", "%d" % value.weight()))
1747 varlist.append(("bold", "bool", "%s" % value.bold()))
1748 varlist.append(("italic", "bool", "%s" % value.italic()))
1749 elif qttype == 'QUrl':
1750 varlist.append(("url", "str", "%s" % value.toString()))
1751 varlist.append(("scheme", "str", "%s" % value.scheme()))
1752 varlist.append(("user", "str", "%s" % value.userName()))
1753 varlist.append(("password", "str", "%s" % value.password()))
1754 varlist.append(("host", "str", "%s" % value.host()))
1755 varlist.append(("port", "int", "%d" % value.port()))
1756 varlist.append(("path", "str", "%s" % value.path()))
1757 elif qttype == 'QModelIndex':
1758 varlist.append(("valid", "bool", "%s" % value.isValid()))
1759 if value.isValid():
1760 varlist.append(("row", "int", "%s" % value.row()))
1761 varlist.append(("column", "int", "%s" % value.column()))
1762 varlist.append(
1763 ("internalId", "int", "%s" % value.internalId()))
1764 varlist.append(
1765 ("internalPointer", "void *", "%s" %
1766 value.internalPointer()))
1767 elif qttype == 'QRegExp':
1768 varlist.append(("pattern", "str", "%s" % value.pattern()))
1769
1770 # GUI stuff
1771 elif qttype == 'QAction':
1772 varlist.append(("name", "str", "%s" % value.objectName()))
1773 varlist.append(("text", "str", "%s" % value.text()))
1774 varlist.append(("icon text", "str", "%s" % value.iconText()))
1775 varlist.append(("tooltip", "str", "%s" % value.toolTip()))
1776 varlist.append(("whatsthis", "str", "%s" % value.whatsThis()))
1777 varlist.append(
1778 ("shortcut", "str", "%s" % value.shortcut().toString()))
1779 elif qttype == 'QKeySequence':
1780 varlist.append(("value", "", "%s" % value.toString()))
1781
1782 # XML stuff
1783 elif qttype == 'QDomAttr':
1784 varlist.append(("name", "str", "%s" % value.name()))
1785 varlist.append(("value", "str", "%s" % value.value()))
1786 elif qttype == 'QDomCharacterData':
1787 varlist.append(("data", "str", "%s" % value.data()))
1788 elif qttype == 'QDomComment':
1789 varlist.append(("data", "str", "%s" % value.data()))
1790 elif qttype == "QDomDocument":
1791 varlist.append(("text", "str", "%s" % value.toString()))
1792 elif qttype == 'QDomElement':
1793 varlist.append(("tagName", "str", "%s" % value.tagName()))
1794 varlist.append(("text", "str", "%s" % value.text()))
1795 elif qttype == 'QDomText':
1796 varlist.append(("data", "str", "%s" % value.data()))
1797
1798 # Networking stuff
1799 elif qttype == 'QHostAddress':
1800 varlist.append(
1801 ("address", "QHostAddress", "%s" % value.toString()))
1802
1803 return varlist
1804
1805 def __formatVariablesList(self, keylist, dict, scope, filter=[],
1806 formatSequences=0):
1807 """
1808 Private method to produce a formated variables list.
1809
1810 The dictionary passed in to it is scanned. Variables are
1811 only added to the list, if their type is not contained
1812 in the filter list and their name doesn't match any of
1813 the filter expressions. The formated variables list (a list of tuples
1814 of 3 values) is returned.
1815
1816 @param keylist keys of the dictionary
1817 @param dict the dictionary to be scanned
1818 @param scope 1 to filter using the globals filter, 0 using the locals
1819 filter (int).
1820 Variables are only added to the list, if their name do not match
1821 any of the filter expressions.
1822 @param filter the indices of variable types to be filtered. Variables
1823 are only added to the list, if their type is not contained in the
1824 filter list.
1825 @param formatSequences flag indicating, that sequence or dictionary
1826 variables should be formatted. If it is 0 (or false), just the
1827 number of items contained in these variables is returned. (boolean)
1828 @return A tuple consisting of a list of formatted variables. Each
1829 variable entry is a tuple of three elements, the variable name,
1830 its type and value.
1831 """
1832 varlist = []
1833 if scope:
1834 patternFilterObjects = self.globalsFilterObjects
1835 else:
1836 patternFilterObjects = self.localsFilterObjects
1837
1838 for key in keylist:
1839 # filter based on the filter pattern
1840 matched = False
1841 for pat in patternFilterObjects:
1842 if pat.match(unicode(key)):
1843 matched = True
1844 break
1845 if matched:
1846 continue
1847
1848 # filter hidden attributes (filter #0)
1849 if 0 in filter and unicode(key)[:2] == '__':
1850 continue
1851
1852 # special handling for '__builtins__' (it's way too big)
1853 if key == '__builtins__':
1854 rvalue = '<module __builtin__ (built-in)>'
1855 valtype = 'module'
1856 else:
1857 value = dict[key]
1858 valtypestr = ("%s" % type(value))[1:-1]
1859
1860 if valtypestr.split(' ', 1)[0] == 'class':
1861 # handle new class type of python 2.2+
1862 if ConfigVarTypeStrings.index('instance') in filter:
1863 continue
1864 valtype = valtypestr
1865 else:
1866 valtype = valtypestr[6:-1]
1867 try:
1868 if ConfigVarTypeStrings.index(valtype) in filter:
1869 continue
1870 except ValueError:
1871 if valtype == "classobj":
1872 if ConfigVarTypeStrings.index(
1873 'instance') in filter:
1874 continue
1875 elif valtype == "sip.methoddescriptor":
1876 if ConfigVarTypeStrings.index(
1877 'instance method') in filter:
1878 continue
1879 elif valtype == "sip.enumtype":
1880 if ConfigVarTypeStrings.index('class') in filter:
1881 continue
1882 elif not valtype.startswith("PySide") and \
1883 ConfigVarTypeStrings.index('other') in filter:
1884 continue
1885
1886 try:
1887 if valtype not in ['list', 'tuple', 'dict']:
1888 rvalue = repr(value)
1889 if valtype.startswith('class') and \
1890 rvalue[0] in ['{', '(', '[']:
1891 rvalue = ""
1892 else:
1893 if valtype == 'dict':
1894 rvalue = "%d" % len(value.keys())
1895 else:
1896 rvalue = "%d" % len(value)
1897 except Exception:
1898 rvalue = ''
1899
1900 if formatSequences:
1901 if unicode(key) == key:
1902 key = "'%s'" % key
1903 else:
1904 key = unicode(key)
1905 varlist.append((key, valtype, rvalue))
1906
1907 return varlist
1908
1909 def __generateFilterObjects(self, scope, filterString):
1910 """
1911 Private slot to convert a filter string to a list of filter objects.
1912
1913 @param scope 1 to generate filter for global variables, 0 for local
1914 variables (int)
1915 @param filterString string of filter patterns separated by ';'
1916 """
1917 patternFilterObjects = []
1918 for pattern in filterString.split(';'):
1919 patternFilterObjects.append(re.compile('^%s$' % pattern))
1920 if scope:
1921 self.globalsFilterObjects = patternFilterObjects[:]
1922 else:
1923 self.localsFilterObjects = patternFilterObjects[:]
1924
1925 def __completionList(self, text):
1926 """
1927 Private slot to handle the request for a commandline completion list.
1928
1929 @param text the text to be completed (string)
1930 """
1931 completerDelims = ' \t\n`~!@#$%^&*()-=+[{]}\\|;:\'",<>/?'
1932
1933 completions = set()
1934 # find position of last delim character
1935 pos = -1
1936 while pos >= -len(text):
1937 if text[pos] in completerDelims:
1938 if pos == -1:
1939 text = ''
1940 else:
1941 text = text[pos + 1:]
1942 break
1943 pos -= 1
1944
1945 # Get local and global completions
1946 try:
1947 localdict = self.currentThread.getFrameLocals(self.framenr)
1948 localCompleter = Completer(localdict).complete
1949 self.__getCompletionList(text, localCompleter, completions)
1950 except AttributeError:
1951 pass
1952 self.__getCompletionList(text, self.complete, completions)
1953
1954 self.sendJsonCommand("ResponseCompletion", {
1955 "completions": list(completions),
1956 "text": text,
1957 })
1958
1959 def __getCompletionList(self, text, completer, completions):
1960 """
1961 Private method to create a completions list.
1962
1963 @param text text to complete (string)
1964 @param completer completer methode
1965 @param completions set where to add new completions strings (set)
1966 """
1967 state = 0
1968 try:
1969 comp = completer(text, state)
1970 except Exception:
1971 comp = None
1972 while comp is not None:
1973 completions.add(comp)
1974 state += 1
1975 try:
1976 comp = completer(text, state)
1977 except Exception:
1978 comp = None
1979
1980 def startDebugger(self, filename=None, host=None, port=None,
1981 enableTrace=True, exceptions=True, tracePython=False,
1982 redirect=True):
1983 """
1984 Public method used to start the remote debugger.
1985
1986 @param filename the program to be debugged (string)
1987 @param host hostname of the debug server (string)
1988 @param port portnumber of the debug server (int)
1989 @param enableTrace flag to enable the tracing function (boolean)
1990 @param exceptions flag to enable exception reporting of the IDE
1991 (boolean)
1992 @param tracePython flag to enable tracing into the Python library
1993 (boolean)
1994 @param redirect flag indicating redirection of stdin, stdout and
1995 stderr (boolean)
1996 """
1997 global debugClient
1998 if host is None:
1999 host = os.getenv('ERICHOST', 'localhost')
2000 if port is None:
2001 port = os.getenv('ERICPORT', 42424)
2002
2003 remoteAddress = self.__resolveHost(host)
2004 self.connectDebugger(port, remoteAddress, redirect)
2005 if filename is not None:
2006 self.running = os.path.abspath(filename)
2007 else:
2008 try:
2009 self.running = os.path.abspath(sys.argv[0])
2010 except IndexError:
2011 self.running = None
2012 if self.running:
2013 self.__setCoding(self.running)
2014 self.passive = True
2015 self.sendPassiveStartup(self.running, exceptions)
2016 self.__interact()
2017
2018 # setup the debugger variables
2019 self._fncache = {}
2020 self.dircache = []
2021 self.mainFrame = None
2022 self.debugging = True
2023
2024 self.attachThread(mainThread=True)
2025 self.mainThread.tracePython = tracePython
2026
2027 # set the system exception handling function to ensure, that
2028 # we report on all unhandled exceptions
2029 sys.excepthook = self.__unhandled_exception
2030 self.__interceptSignals()
2031
2032 # now start debugging
2033 if enableTrace:
2034 self.mainThread.set_trace()
2035
2036 def startProgInDebugger(self, progargs, wd='', host=None,
2037 port=None, exceptions=True, tracePython=False,
2038 redirect=True):
2039 """
2040 Public method used to start the remote debugger.
2041
2042 @param progargs commandline for the program to be debugged
2043 (list of strings)
2044 @param wd working directory for the program execution (string)
2045 @param host hostname of the debug server (string)
2046 @param port portnumber of the debug server (int)
2047 @param exceptions flag to enable exception reporting of the IDE
2048 (boolean)
2049 @param tracePython flag to enable tracing into the Python library
2050 (boolean)
2051 @param redirect flag indicating redirection of stdin, stdout and
2052 stderr (boolean)
2053 """
2054 if host is None:
2055 host = os.getenv('ERICHOST', 'localhost')
2056 if port is None:
2057 port = os.getenv('ERICPORT', 42424)
2058
2059 remoteAddress = self.__resolveHost(host)
2060 self.connectDebugger(port, remoteAddress, redirect)
2061
2062 self._fncache = {}
2063 self.dircache = []
2064 sys.argv = progargs[:]
2065 sys.argv[0] = os.path.abspath(sys.argv[0])
2066 sys.path = self.__getSysPath(os.path.dirname(sys.argv[0]))
2067 if wd == '':
2068 os.chdir(sys.path[1])
2069 else:
2070 os.chdir(wd)
2071 self.running = sys.argv[0]
2072 self.__setCoding(self.running)
2073 self.mainFrame = None
2074 self.debugging = True
2075
2076 self.passive = True
2077 self.sendPassiveStartup(self.running, exceptions)
2078 self.__interact()
2079
2080 self.attachThread(mainThread=1)
2081 self.mainThread.tracePython = tracePython
2082
2083 # set the system exception handling function to ensure, that
2084 # we report on all unhandled exceptions
2085 sys.excepthook = self.__unhandled_exception
2086 self.__interceptSignals()
2087
2088 # This will eventually enter a local event loop.
2089 # Note the use of backquotes to cause a repr of self.running. The
2090 # need for this is on Windows os where backslash is the path separator.
2091 # They will get inadvertantly stripped away during the eval causing
2092 # IOErrors if self.running is passed as a normal str.
2093 self.debugMod.__dict__['__file__'] = self.running
2094 sys.modules['__main__'] = self.debugMod
2095 res = self.mainThread.run('execfile(' + repr(self.running) + ')',
2096 self.debugMod.__dict__)
2097 self.progTerminated(res)
2098
2099 def run_call(self, scriptname, func, *args):
2100 """
2101 Public method used to start the remote debugger and call a function.
2102
2103 @param scriptname name of the script to be debugged (string)
2104 @param func function to be called
2105 @param *args arguments being passed to func
2106 @return result of the function call
2107 """
2108 self.startDebugger(scriptname, enableTrace=0)
2109 res = self.mainThread.runcall(func, *args)
2110 self.progTerminated(res)
2111 return res
2112
2113 def __resolveHost(self, host):
2114 """
2115 Private method to resolve a hostname to an IP address.
2116
2117 @param host hostname of the debug server (string)
2118 @return IP address (string)
2119 """
2120 try:
2121 host, version = host.split("@@")
2122 except ValueError:
2123 version = 'v4'
2124 if version == 'v4':
2125 family = socket.AF_INET
2126 else:
2127 family = socket.AF_INET6
2128 return socket.getaddrinfo(host, None, family,
2129 socket.SOCK_STREAM)[0][4][0]
2130
2131 def main(self):
2132 """
2133 Public method implementing the main method.
2134 """
2135 if '--' in sys.argv:
2136 args = sys.argv[1:]
2137 host = None
2138 port = None
2139 wd = ''
2140 tracePython = False
2141 exceptions = True
2142 redirect = True
2143 while args[0]:
2144 if args[0] == '-h':
2145 host = args[1]
2146 del args[0]
2147 del args[0]
2148 elif args[0] == '-p':
2149 port = int(args[1])
2150 del args[0]
2151 del args[0]
2152 elif args[0] == '-w':
2153 wd = args[1]
2154 del args[0]
2155 del args[0]
2156 elif args[0] == '-t':
2157 tracePython = True
2158 del args[0]
2159 elif args[0] == '-e':
2160 exceptions = False
2161 del args[0]
2162 elif args[0] == '-n':
2163 redirect = False
2164 del args[0]
2165 elif args[0] == '--no-encoding':
2166 self.noencoding = True
2167 del args[0]
2168 elif args[0] == '--fork-child':
2169 self.fork_auto = True
2170 self.fork_child = True
2171 del args[0]
2172 elif args[0] == '--fork-parent':
2173 self.fork_auto = True
2174 self.fork_child = False
2175 del args[0]
2176 elif args[0] == '--':
2177 del args[0]
2178 break
2179 else: # unknown option
2180 del args[0]
2181 if not args:
2182 print "No program given. Aborting!" # __IGNORE_WARNING__
2183 else:
2184 if not self.noencoding:
2185 self.__coding = self.defaultCoding
2186 self.startProgInDebugger(args, wd, host, port,
2187 exceptions=exceptions,
2188 tracePython=tracePython,
2189 redirect=redirect)
2190 else:
2191 if sys.argv[1] == '--no-encoding':
2192 self.noencoding = True
2193 del sys.argv[1]
2194 if sys.argv[1] == '':
2195 del sys.argv[1]
2196 try:
2197 port = int(sys.argv[1])
2198 except (ValueError, IndexError):
2199 port = -1
2200 try:
2201 redirect = int(sys.argv[2])
2202 except (ValueError, IndexError):
2203 redirect = True
2204 try:
2205 ipOrHost = sys.argv[3]
2206 if ':' in ipOrHost:
2207 remoteAddress = ipOrHost
2208 elif ipOrHost[0] in '0123456789':
2209 remoteAddress = ipOrHost
2210 else:
2211 remoteAddress = self.__resolveHost(ipOrHost)
2212 except Exception:
2213 remoteAddress = None
2214 sys.argv = ['']
2215 if '' not in sys.path:
2216 sys.path.insert(0, '')
2217 if port >= 0:
2218 if not self.noencoding:
2219 self.__coding = self.defaultCoding
2220 self.connectDebugger(port, remoteAddress, redirect)
2221 self.__interact()
2222 else:
2223 print "No network port given. Aborting..." # __IGNORE_WARNING__
2224
2225 def fork(self):
2226 """
2227 Public method implementing a fork routine deciding which branch to
2228 follow.
2229
2230 @return process ID (integer)
2231 """
2232 if not self.fork_auto:
2233 self.sendJsonCommand("RequestForkTo", {})
2234 self.eventLoop(True)
2235 pid = DebugClientOrigFork()
2236 if pid == 0:
2237 # child
2238 if not self.fork_child:
2239 sys.settrace(None)
2240 sys.setprofile(None)
2241 self.sessionClose(0)
2242 else:
2243 # parent
2244 if self.fork_child:
2245 sys.settrace(None)
2246 sys.setprofile(None)
2247 self.sessionClose(0)
2248 return pid
2249
2250 def close(self, fd):
2251 """
2252 Public method implementing a close method as a replacement for
2253 os.close().
2254
2255 It prevents the debugger connections from being closed.
2256
2257 @param fd file descriptor to be closed (integer)
2258 """
2259 if fd in [self.readstream.fileno(), self.writestream.fileno(),
2260 self.errorstream.fileno()]:
2261 return
2262
2263 DebugClientOrigClose(fd)
2264
2265 def __getSysPath(self, firstEntry):
2266 """
2267 Private slot to calculate a path list including the PYTHONPATH
2268 environment variable.
2269
2270 @param firstEntry entry to be put first in sys.path (string)
2271 @return path list for use as sys.path (list of strings)
2272 """
2273 sysPath = [path for path in os.environ.get("PYTHONPATH", "")
2274 .split(os.pathsep)
2275 if path not in sys.path] + sys.path[:]
2276 if "" in sysPath:
2277 sysPath.remove("")
2278 sysPath.insert(0, firstEntry)
2279 sysPath.insert(0, '')
2280 return sysPath
2281
2282 #
2283 # eflag: FileType = Python2
2284 # eflag: noqa = M601, M702

eric ide

mercurial