eric7/DebugClients/Python/DebugClientBase.py

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

eric ide

mercurial