eric6/DebugClients/Python/DebugClientBase.py

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

eric ide

mercurial