DebugClients/Python2/DebugClientBase.py

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

eric ide

mercurial