eric7/DebugClients/Python/coverage/control.py

branch
eric7
changeset 8991
2fc945191992
parent 8929
fcca2fa618bf
child 9099
0e511e0e94a3
equal deleted inserted replaced
8990:ca8e477c590c 8991:2fc945191992
7 import collections 7 import collections
8 import contextlib 8 import contextlib
9 import os 9 import os
10 import os.path 10 import os.path
11 import platform 11 import platform
12 import signal
12 import sys 13 import sys
14 import threading
13 import time 15 import time
14 import warnings 16 import warnings
15 17
16 from coverage import env 18 from coverage import env
17 from coverage.annotate import AnnotateReporter 19 from coverage.annotate import AnnotateReporter
24 from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError 26 from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError
25 from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory 27 from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
26 from coverage.html import HtmlReporter 28 from coverage.html import HtmlReporter
27 from coverage.inorout import InOrOut 29 from coverage.inorout import InOrOut
28 from coverage.jsonreport import JsonReporter 30 from coverage.jsonreport import JsonReporter
29 from coverage.misc import bool_or_none, join_regex, human_sorted, human_sorted_items 31 from coverage.lcovreport import LcovReporter
32 from coverage.misc import bool_or_none, join_regex, human_sorted
30 from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module 33 from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
31 from coverage.plugin import FileReporter 34 from coverage.plugin import FileReporter
32 from coverage.plugin_support import Plugins 35 from coverage.plugin_support import Plugins
33 from coverage.python import PythonFileReporter 36 from coverage.python import PythonFileReporter
34 from coverage.report import render_report 37 from coverage.report import render_report
58 yield 61 yield
59 finally: 62 finally:
60 cov.config = original_config 63 cov.config = original_config
61 64
62 65
63 _DEFAULT_DATAFILE = DefaultValue("MISSING") 66 DEFAULT_DATAFILE = DefaultValue("MISSING")
67 _DEFAULT_DATAFILE = DEFAULT_DATAFILE # Just in case, for backwards compatibility
64 68
65 class Coverage: 69 class Coverage:
66 """Programmatic access to coverage.py. 70 """Programmatic access to coverage.py.
67 71
68 To use:: 72 To use::
99 return cls._instances[-1] 103 return cls._instances[-1]
100 else: 104 else:
101 return None 105 return None
102 106
103 def __init__( 107 def __init__(
104 self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None, 108 self, data_file=DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None,
105 auto_data=False, timid=None, branch=None, config_file=True, 109 auto_data=False, timid=None, branch=None, config_file=True,
106 source=None, source_pkgs=None, omit=None, include=None, debug=None, 110 source=None, source_pkgs=None, omit=None, include=None, debug=None,
107 concurrency=None, check_preimported=False, context=None, 111 concurrency=None, check_preimported=False, context=None,
108 messages=False, 112 messages=False,
109 ): # pylint: disable=too-many-arguments 113 ): # pylint: disable=too-many-arguments
196 200
197 """ 201 """
198 # data_file=None means no disk file at all. data_file missing means 202 # data_file=None means no disk file at all. data_file missing means
199 # use the value from the config file. 203 # use the value from the config file.
200 self._no_disk = data_file is None 204 self._no_disk = data_file is None
201 if data_file is _DEFAULT_DATAFILE: 205 if data_file is DEFAULT_DATAFILE:
202 data_file = None 206 data_file = None
203 207
204 self.config = None 208 self.config = None
205 209
206 # This is injectable by tests. 210 # This is injectable by tests.
225 self._inorout = None 229 self._inorout = None
226 self._data_suffix = self._run_suffix = None 230 self._data_suffix = self._run_suffix = None
227 self._exclude_re = None 231 self._exclude_re = None
228 self._debug = None 232 self._debug = None
229 self._file_mapper = None 233 self._file_mapper = None
234 self._old_sigterm = None
230 235
231 # State machine variables: 236 # State machine variables:
232 # Have we initialized everything? 237 # Have we initialized everything?
233 self._inited = False 238 self._inited = False
234 self._inited_for_start = False 239 self._inited_for_start = False
308 313
309 def _write_startup_debug(self): 314 def _write_startup_debug(self):
310 """Write out debug info at startup if needed.""" 315 """Write out debug info at startup if needed."""
311 wrote_any = False 316 wrote_any = False
312 with self._debug.without_callers(): 317 with self._debug.without_callers():
313 if self._debug.should('config'): 318 if self._debug.should("config"):
314 config_info = human_sorted_items(self.config.__dict__.items()) 319 config_info = self.config.debug_info()
315 config_info = [(k, v) for k, v in config_info if not k.startswith('_')] 320 write_formatted_info(self._debug.write, "config", config_info)
316 write_formatted_info(self._debug, "config", config_info)
317 wrote_any = True 321 wrote_any = True
318 322
319 if self._debug.should('sys'): 323 if self._debug.should("sys"):
320 write_formatted_info(self._debug, "sys", self.sys_info()) 324 write_formatted_info(self._debug.write, "sys", self.sys_info())
321 for plugin in self._plugins: 325 for plugin in self._plugins:
322 header = "sys: " + plugin._coverage_plugin_name 326 header = "sys: " + plugin._coverage_plugin_name
323 info = plugin.sys_info() 327 info = plugin.sys_info()
324 write_formatted_info(self._debug, header, info) 328 write_formatted_info(self._debug.write, header, info)
325 wrote_any = True 329 wrote_any = True
326 330
331 if self._debug.should("pybehave"):
332 write_formatted_info(self._debug.write, "pybehave", env.debug_info())
333 wrote_any = True
334
327 if wrote_any: 335 if wrote_any:
328 write_formatted_info(self._debug, "end", ()) 336 write_formatted_info(self._debug.write, "end", ())
329 337
330 def _should_trace(self, filename, frame): 338 def _should_trace(self, filename, frame):
331 """Decide whether to trace execution in `filename`. 339 """Decide whether to trace execution in `filename`.
332 340
333 Calls `_should_trace_internal`, and returns the FileDisposition. 341 Calls `_should_trace_internal`, and returns the FileDisposition.
452 if "multiprocessing" in concurrency: 460 if "multiprocessing" in concurrency:
453 if not patch_multiprocessing: 461 if not patch_multiprocessing:
454 raise ConfigError( # pragma: only jython 462 raise ConfigError( # pragma: only jython
455 "multiprocessing is not supported on this Python" 463 "multiprocessing is not supported on this Python"
456 ) 464 )
465 if self.config.config_file is None:
466 raise ConfigError("multiprocessing requires a configuration file")
457 patch_multiprocessing(rcfile=self.config.config_file) 467 patch_multiprocessing(rcfile=self.config.config_file)
458 468
459 dycon = self.config.dynamic_context 469 dycon = self.config.dynamic_context
460 if not dycon or dycon == "none": 470 if not dycon or dycon == "none":
461 context_switchers = [] 471 context_switchers = []
522 self._inorout.disp_class = self._collector.file_disposition_class 532 self._inorout.disp_class = self._collector.file_disposition_class
523 533
524 # It's useful to write debug info after initing for start. 534 # It's useful to write debug info after initing for start.
525 self._should_write_debug = True 535 self._should_write_debug = True
526 536
537 # Register our clean-up handlers.
527 atexit.register(self._atexit) 538 atexit.register(self._atexit)
539 is_main = (threading.current_thread() == threading.main_thread())
540 if is_main and not env.WINDOWS:
541 # The Python docs seem to imply that SIGTERM works uniformly even
542 # on Windows, but that's not my experience, and this agrees:
543 # https://stackoverflow.com/questions/35772001/x/35792192#35792192
544 self._old_sigterm = signal.signal(signal.SIGTERM, self._on_sigterm)
528 545
529 def _init_data(self, suffix): 546 def _init_data(self, suffix):
530 """Create a data file if we don't have one yet.""" 547 """Create a data file if we don't have one yet."""
531 if self._data is None: 548 if self._data is None:
532 # Create the data file. We do this at construction time so that the 549 # Create the data file. We do this at construction time so that the
580 self._instances.pop() 597 self._instances.pop()
581 if self._started: 598 if self._started:
582 self._collector.stop() 599 self._collector.stop()
583 self._started = False 600 self._started = False
584 601
585 def _atexit(self): 602 def _atexit(self, event="atexit"):
586 """Clean up on process shutdown.""" 603 """Clean up on process shutdown."""
587 if self._debug.should("process"): 604 if self._debug.should("process"):
588 self._debug.write(f"atexit: pid: {os.getpid()}, instance: {self!r}") 605 self._debug.write(f"{event}: pid: {os.getpid()}, instance: {self!r}")
589 if self._started: 606 if self._started:
590 self.stop() 607 self.stop()
591 if self._auto_save: 608 if self._auto_save:
592 self.save() 609 self.save()
610
611 def _on_sigterm(self, signum_unused, frame_unused):
612 """A handler for signal.SIGTERM."""
613 self._atexit("sigterm")
614 # Statements after here won't be seen by metacov because we just wrote
615 # the data, and are about to kill the process.
616 signal.signal(signal.SIGTERM, self._old_sigterm) # pragma: not covered
617 os.kill(os.getpid(), signal.SIGTERM) # pragma: not covered
593 618
594 def erase(self): 619 def erase(self):
595 """Erase previously collected coverage data. 620 """Erase previously collected coverage data.
596 621
597 This removes the in-memory data collected in this session as well as 622 This removes the in-memory data collected in this session as well as
1046 ignore_errors=ignore_errors, report_omit=omit, report_include=include, 1071 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1047 json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print, 1072 json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print,
1048 json_show_contexts=show_contexts 1073 json_show_contexts=show_contexts
1049 ): 1074 ):
1050 return render_report(self.config.json_output, JsonReporter(self), morfs, self._message) 1075 return render_report(self.config.json_output, JsonReporter(self), morfs, self._message)
1076
1077 def lcov_report(
1078 self, morfs=None, outfile=None, ignore_errors=None,
1079 omit=None, include=None, contexts=None,
1080 ):
1081 """Generate an LCOV report of coverage results.
1082
1083 Each module in 'morfs' is included in the report. 'outfile' is the
1084 path to write the file to, "-" will write to stdout.
1085
1086 See :meth 'report' for other arguments.
1087
1088 .. versionadded:: 6.3
1089 """
1090 with override_config(self,
1091 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1092 lcov_output=outfile, report_contexts=contexts,
1093 ):
1094 return render_report(self.config.lcov_output, LcovReporter(self), morfs, self._message)
1051 1095
1052 def sys_info(self): 1096 def sys_info(self):
1053 """Return a list of (key, value) pairs showing internal information.""" 1097 """Return a list of (key, value) pairs showing internal information."""
1054 1098
1055 import coverage as covmod 1099 import coverage as covmod

eric ide

mercurial