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

eric ide

mercurial