eric7/DebugClients/Python/coverage/collector.py

branch
eric7
changeset 8775
0802ae193343
parent 8527
2bd1325d727e
child 8929
fcca2fa618bf
equal deleted inserted replaced
8774:d728227e8ebb 8775:0802ae193343
5 5
6 import os 6 import os
7 import sys 7 import sys
8 8
9 from coverage import env 9 from coverage import env
10 from coverage.backward import litems, range # pylint: disable=redefined-builtin
11 from coverage.debug import short_stack 10 from coverage.debug import short_stack
12 from coverage.disposition import FileDisposition 11 from coverage.disposition import FileDisposition
13 from coverage.misc import CoverageException, isolate_module 12 from coverage.exceptions import CoverageException
13 from coverage.misc import human_sorted, isolate_module
14 from coverage.pytracer import PyTracer 14 from coverage.pytracer import PyTracer
15 15
16 os = isolate_module(os) 16 os = isolate_module(os)
17 17
18 18
19 try: 19 try:
20 # Use the C extension code when we can, for speed. 20 # Use the C extension code when we can, for speed.
21 from coverage.tracer import CTracer, CFileDisposition 21 from coverage.tracer import CTracer, CFileDisposition
22 except ImportError: 22 except ImportError:
23 # Couldn't import the C extension, maybe it isn't built. 23 # Couldn't import the C extension, maybe it isn't built.
24 if os.getenv('COVERAGE_TEST_TRACER') == 'c': 24 if os.getenv('COVERAGE_TEST_TRACER') == 'c': # pragma: part covered
25 # During testing, we use the COVERAGE_TEST_TRACER environment variable 25 # During testing, we use the COVERAGE_TEST_TRACER environment variable
26 # to indicate that we've fiddled with the environment to test this 26 # to indicate that we've fiddled with the environment to test this
27 # fallback code. If we thought we had a C tracer, but couldn't import 27 # fallback code. If we thought we had a C tracer, but couldn't import
28 # it, then exit quickly and clearly instead of dribbling confusing 28 # it, then exit quickly and clearly instead of dribbling confusing
29 # errors. I'm using sys.exit here instead of an exception because an 29 # errors. I'm using sys.exit here instead of an exception because an
31 sys.stderr.write("*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n") 31 sys.stderr.write("*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n")
32 sys.exit(1) 32 sys.exit(1)
33 CTracer = None 33 CTracer = None
34 34
35 35
36 class Collector(object): 36 class Collector:
37 """Collects trace data. 37 """Collects trace data.
38 38
39 Creates a Tracer object for each thread, since they track stack 39 Creates a Tracer object for each thread, since they track stack
40 information. Each Tracer points to the same shared data, contributing 40 information. Each Tracer points to the same shared data, contributing
41 traced data points. 41 traced data points.
114 self.mapped_file_cache = {} 114 self.mapped_file_cache = {}
115 115
116 # We can handle a few concurrency options here, but only one at a time. 116 # We can handle a few concurrency options here, but only one at a time.
117 these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency) 117 these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency)
118 if len(these_concurrencies) > 1: 118 if len(these_concurrencies) > 1:
119 raise CoverageException("Conflicting concurrency settings: %s" % concurrency) 119 raise CoverageException(f"Conflicting concurrency settings: {concurrency}")
120 self.concurrency = these_concurrencies.pop() if these_concurrencies else '' 120 self.concurrency = these_concurrencies.pop() if these_concurrencies else ''
121 121
122 try: 122 try:
123 if self.concurrency == "greenlet": 123 if self.concurrency == "greenlet":
124 import greenlet 124 import greenlet
134 # it's imported early, and the program being measured uses 134 # it's imported early, and the program being measured uses
135 # gevent, then gevent's monkey-patching won't work properly. 135 # gevent, then gevent's monkey-patching won't work properly.
136 import threading 136 import threading
137 self.threading = threading 137 self.threading = threading
138 else: 138 else:
139 raise CoverageException("Don't understand concurrency=%s" % concurrency) 139 raise CoverageException(f"Don't understand concurrency={concurrency}")
140 except ImportError: 140 except ImportError as ex:
141 raise CoverageException( 141 raise CoverageException(
142 "Couldn't trace with concurrency=%s, the module isn't installed." % ( 142 "Couldn't trace with concurrency={}, the module isn't installed.".format(
143 self.concurrency, 143 self.concurrency,
144 ) 144 )
145 ) 145 ) from ex
146 146
147 self.reset() 147 self.reset()
148 148
149 if timid: 149 if timid:
150 # Being timid: use the simple Python trace function. 150 # Being timid: use the simple Python trace function.
155 self._trace_class = CTracer or PyTracer 155 self._trace_class = CTracer or PyTracer
156 156
157 if self._trace_class is CTracer: 157 if self._trace_class is CTracer:
158 self.file_disposition_class = CFileDisposition 158 self.file_disposition_class = CFileDisposition
159 self.supports_plugins = True 159 self.supports_plugins = True
160 self.packed_arcs = True
160 else: 161 else:
161 self.file_disposition_class = FileDisposition 162 self.file_disposition_class = FileDisposition
162 self.supports_plugins = False 163 self.supports_plugins = False
164 self.packed_arcs = False
163 165
164 def __repr__(self): 166 def __repr__(self):
165 return "<Collector at 0x%x: %s>" % (id(self), self.tracer_name()) 167 return f"<Collector at 0x{id(self):x}: {self.tracer_name()}>"
166 168
167 def use_data(self, covdata, context): 169 def use_data(self, covdata, context):
168 """Use `covdata` for recording data.""" 170 """Use `covdata` for recording data."""
169 self.covdata = covdata 171 self.covdata = covdata
170 self.static_context = context 172 self.static_context = context
242 244
243 if hasattr(tracer, 'concur_id_func'): 245 if hasattr(tracer, 'concur_id_func'):
244 tracer.concur_id_func = self.concur_id_func 246 tracer.concur_id_func = self.concur_id_func
245 elif self.concur_id_func: 247 elif self.concur_id_func:
246 raise CoverageException( 248 raise CoverageException(
247 "Can't support concurrency=%s with %s, only threads are supported" % ( 249 "Can't support concurrency={} with {}, only threads are supported".format(
248 self.concurrency, self.tracer_name(), 250 self.concurrency, self.tracer_name(),
249 ) 251 )
250 ) 252 )
251 253
252 if hasattr(tracer, 'file_tracers'): 254 if hasattr(tracer, 'file_tracers'):
316 # Replay all the events from fullcoverage into the new trace function. 318 # Replay all the events from fullcoverage into the new trace function.
317 for args in traces0: 319 for args in traces0:
318 (frame, event, arg), lineno = args 320 (frame, event, arg), lineno = args
319 try: 321 try:
320 fn(frame, event, arg, lineno=lineno) 322 fn(frame, event, arg, lineno=lineno)
321 except TypeError: 323 except TypeError as ex:
322 raise Exception("fullcoverage must be run with the C trace function.") 324 raise Exception("fullcoverage must be run with the C trace function.") from ex
323 325
324 # Install our installation tracer in threading, to jump-start other 326 # Install our installation tracer in threading, to jump-start other
325 # threads. 327 # threads.
326 if self.threading: 328 if self.threading:
327 self.threading.settrace(self._installation_trace) 329 self.threading.settrace(self._installation_trace)
330 """Stop collecting trace information.""" 332 """Stop collecting trace information."""
331 assert self._collectors 333 assert self._collectors
332 if self._collectors[-1] is not self: 334 if self._collectors[-1] is not self:
333 print("self._collectors:") 335 print("self._collectors:")
334 for c in self._collectors: 336 for c in self._collectors:
335 print(" {!r}\n{}".format(c, c.origin)) 337 print(f" {c!r}\n{c.origin}")
336 assert self._collectors[-1] is self, ( 338 assert self._collectors[-1] is self, (
337 "Expected current collector to be %r, but it's %r" % (self, self._collectors[-1]) 339 f"Expected current collector to be {self!r}, but it's {self._collectors[-1]!r}"
338 ) 340 )
339 341
340 self.pause() 342 self.pause()
341 343
342 # Remove this Collector from the stack, and resume the one underneath 344 # Remove this Collector from the stack, and resume the one underneath
350 for tracer in self.tracers: 352 for tracer in self.tracers:
351 tracer.stop() 353 tracer.stop()
352 stats = tracer.get_stats() 354 stats = tracer.get_stats()
353 if stats: 355 if stats:
354 print("\nCoverage.py tracer stats:") 356 print("\nCoverage.py tracer stats:")
355 for k in sorted(stats.keys()): 357 for k in human_sorted(stats.keys()):
356 print("%20s: %s" % (k, stats[k])) 358 print(f"{k:>20}: {stats[k]}")
357 if self.threading: 359 if self.threading:
358 self.threading.settrace(None) 360 self.threading.settrace(None)
359 361
360 def resume(self): 362 def resume(self):
361 """Resume tracing after a `pause`.""" 363 """Resume tracing after a `pause`."""
388 def disable_plugin(self, disposition): 390 def disable_plugin(self, disposition):
389 """Disable the plugin mentioned in `disposition`.""" 391 """Disable the plugin mentioned in `disposition`."""
390 file_tracer = disposition.file_tracer 392 file_tracer = disposition.file_tracer
391 plugin = file_tracer._coverage_plugin 393 plugin = file_tracer._coverage_plugin
392 plugin_name = plugin._coverage_plugin_name 394 plugin_name = plugin._coverage_plugin_name
393 self.warn("Disabling plug-in {!r} due to previous exception".format(plugin_name)) 395 self.warn(f"Disabling plug-in {plugin_name!r} due to previous exception")
394 plugin._coverage_enabled = False 396 plugin._coverage_enabled = False
395 disposition.trace = False 397 disposition.trace = False
396 398
397 def cached_mapped_file(self, filename): 399 def cached_mapped_file(self, filename):
398 """A locally cached version of file names mapped through file_mapper.""" 400 """A locally cached version of file names mapped through file_mapper."""
402 except KeyError: 404 except KeyError:
403 return self.mapped_file_cache.setdefault(key, self.file_mapper(filename)) 405 return self.mapped_file_cache.setdefault(key, self.file_mapper(filename))
404 406
405 def mapped_file_dict(self, d): 407 def mapped_file_dict(self, d):
406 """Return a dict like d, but with keys modified by file_mapper.""" 408 """Return a dict like d, but with keys modified by file_mapper."""
407 # The call to litems() ensures that the GIL protects the dictionary 409 # The call to list(items()) ensures that the GIL protects the dictionary
408 # iterator against concurrent modifications by tracers running 410 # iterator against concurrent modifications by tracers running
409 # in other threads. We try three times in case of concurrent 411 # in other threads. We try three times in case of concurrent
410 # access, hoping to get a clean copy. 412 # access, hoping to get a clean copy.
411 runtime_err = None 413 runtime_err = None
412 for _ in range(3): 414 for _ in range(3): # pragma: part covered
413 try: 415 try:
414 items = litems(d) 416 items = list(d.items())
415 except RuntimeError as ex: 417 except RuntimeError as ex: # pragma: cant happen
416 runtime_err = ex 418 runtime_err = ex
417 else: 419 else:
418 break 420 break
419 else: 421 else:
420 raise runtime_err 422 raise runtime_err # pragma: cant happen
421 423
422 return dict((self.cached_mapped_file(k), v) for k, v in items if v) 424 return {self.cached_mapped_file(k): v for k, v in items if v}
423 425
424 def plugin_was_disabled(self, plugin): 426 def plugin_was_disabled(self, plugin):
425 """Record that `plugin` was disabled during the run.""" 427 """Record that `plugin` was disabled during the run."""
426 self.disabled_plugins.add(plugin._coverage_plugin_name) 428 self.disabled_plugins.add(plugin._coverage_plugin_name)
427 429
435 """ 437 """
436 if not self._activity(): 438 if not self._activity():
437 return False 439 return False
438 440
439 if self.branch: 441 if self.branch:
440 self.covdata.add_arcs(self.mapped_file_dict(self.data)) 442 if self.packed_arcs:
443 # Unpack the line number pairs packed into integers. See
444 # tracer.c:CTracer_record_pair for the C code that creates
445 # these packed ints.
446 data = {}
447 for fname, packeds in self.data.items():
448 tuples = []
449 for packed in packeds:
450 l1 = packed & 0xFFFFF
451 l2 = (packed & (0xFFFFF << 20)) >> 20
452 if packed & (1 << 40):
453 l1 *= -1
454 if packed & (1 << 41):
455 l2 *= -1
456 tuples.append((l1, l2))
457 data[fname] = tuples
458 else:
459 data = self.data
460 self.covdata.add_arcs(self.mapped_file_dict(data))
441 else: 461 else:
442 self.covdata.add_lines(self.mapped_file_dict(self.data)) 462 self.covdata.add_lines(self.mapped_file_dict(self.data))
443 463
444 file_tracers = { 464 file_tracers = {
445 k: v for k, v in self.file_tracers.items() 465 k: v for k, v in self.file_tracers.items()

eric ide

mercurial