eric7/DebugClients/Python/coverage/control.py

branch
eric7
changeset 8312
800c432b34c8
parent 7975
7d493839a8fc
child 8527
2bd1325d727e
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
4 """Core control stuff for coverage.py."""
5
6 import atexit
7 import collections
8 import contextlib
9 import os
10 import os.path
11 import platform
12 import sys
13 import time
14
15 from coverage import env
16 from coverage.annotate import AnnotateReporter
17 from coverage.backward import string_class, iitems
18 from coverage.collector import Collector, CTracer
19 from coverage.config import read_coverage_config
20 from coverage.context import should_start_context_test_function, combine_context_switchers
21 from coverage.data import CoverageData, combine_parallel_data
22 from coverage.debug import DebugControl, short_stack, write_formatted_info
23 from coverage.disposition import disposition_debug_msg
24 from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
25 from coverage.html import HtmlReporter
26 from coverage.inorout import InOrOut
27 from coverage.jsonreport import JsonReporter
28 from coverage.misc import CoverageException, bool_or_none, join_regex
29 from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
30 from coverage.plugin import FileReporter
31 from coverage.plugin_support import Plugins
32 from coverage.python import PythonFileReporter
33 from coverage.report import render_report
34 from coverage.results import Analysis, Numbers
35 from coverage.summary import SummaryReporter
36 from coverage.xmlreport import XmlReporter
37
38 try:
39 from coverage.multiproc import patch_multiprocessing
40 except ImportError: # pragma: only jython
41 # Jython has no multiprocessing module.
42 patch_multiprocessing = None
43
44 os = isolate_module(os)
45
46 @contextlib.contextmanager
47 def override_config(cov, **kwargs):
48 """Temporarily tweak the configuration of `cov`.
49
50 The arguments are applied to `cov.config` with the `from_args` method.
51 At the end of the with-statement, the old configuration is restored.
52 """
53 original_config = cov.config
54 cov.config = cov.config.copy()
55 try:
56 cov.config.from_args(**kwargs)
57 yield
58 finally:
59 cov.config = original_config
60
61
62 _DEFAULT_DATAFILE = DefaultValue("MISSING")
63
64 class Coverage(object):
65 """Programmatic access to coverage.py.
66
67 To use::
68
69 from coverage import Coverage
70
71 cov = Coverage()
72 cov.start()
73 #.. call your code ..
74 cov.stop()
75 cov.html_report(directory='covhtml')
76
77 Note: in keeping with Python custom, names starting with underscore are
78 not part of the public API. They might stop working at any point. Please
79 limit yourself to documented methods to avoid problems.
80
81 """
82
83 # The stack of started Coverage instances.
84 _instances = []
85
86 @classmethod
87 def current(cls):
88 """Get the latest started `Coverage` instance, if any.
89
90 Returns: a `Coverage` instance, or None.
91
92 .. versionadded:: 5.0
93
94 """
95 if cls._instances:
96 return cls._instances[-1]
97 else:
98 return None
99
100 def __init__(
101 self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None,
102 auto_data=False, timid=None, branch=None, config_file=True,
103 source=None, source_pkgs=None, omit=None, include=None, debug=None,
104 concurrency=None, check_preimported=False, context=None,
105 ): # pylint: disable=too-many-arguments
106 """
107 Many of these arguments duplicate and override values that can be
108 provided in a configuration file. Parameters that are missing here
109 will use values from the config file.
110
111 `data_file` is the base name of the data file to use. The config value
112 defaults to ".coverage". None can be provided to prevent writing a data
113 file. `data_suffix` is appended (with a dot) to `data_file` to create
114 the final file name. If `data_suffix` is simply True, then a suffix is
115 created with the machine and process identity included.
116
117 `cover_pylib` is a boolean determining whether Python code installed
118 with the Python interpreter is measured. This includes the Python
119 standard library and any packages installed with the interpreter.
120
121 If `auto_data` is true, then any existing data file will be read when
122 coverage measurement starts, and data will be saved automatically when
123 measurement stops.
124
125 If `timid` is true, then a slower and simpler trace function will be
126 used. This is important for some environments where manipulation of
127 tracing functions breaks the faster trace function.
128
129 If `branch` is true, then branch coverage will be measured in addition
130 to the usual statement coverage.
131
132 `config_file` determines what configuration file to read:
133
134 * If it is ".coveragerc", it is interpreted as if it were True,
135 for backward compatibility.
136
137 * If it is a string, it is the name of the file to read. If the
138 file can't be read, it is an error.
139
140 * If it is True, then a few standard files names are tried
141 (".coveragerc", "setup.cfg", "tox.ini"). It is not an error for
142 these files to not be found.
143
144 * If it is False, then no configuration file is read.
145
146 `source` is a list of file paths or package names. Only code located
147 in the trees indicated by the file paths or package names will be
148 measured.
149
150 `source_pkgs` is a list of package names. It works the same as
151 `source`, but can be used to name packages where the name can also be
152 interpreted as a file path.
153
154 `include` and `omit` are lists of file name patterns. Files that match
155 `include` will be measured, files that match `omit` will not. Each
156 will also accept a single string argument.
157
158 `debug` is a list of strings indicating what debugging information is
159 desired.
160
161 `concurrency` is a string indicating the concurrency library being used
162 in the measured code. Without this, coverage.py will get incorrect
163 results if these libraries are in use. Valid strings are "greenlet",
164 "eventlet", "gevent", "multiprocessing", or "thread" (the default).
165 This can also be a list of these strings.
166
167 If `check_preimported` is true, then when coverage is started, the
168 already-imported files will be checked to see if they should be
169 measured by coverage. Importing measured files before coverage is
170 started can mean that code is missed.
171
172 `context` is a string to use as the :ref:`static context
173 <static_contexts>` label for collected data.
174
175 .. versionadded:: 4.0
176 The `concurrency` parameter.
177
178 .. versionadded:: 4.2
179 The `concurrency` parameter can now be a list of strings.
180
181 .. versionadded:: 5.0
182 The `check_preimported` and `context` parameters.
183
184 .. versionadded:: 5.3
185 The `source_pkgs` parameter.
186
187 """
188 # data_file=None means no disk file at all. data_file missing means
189 # use the value from the config file.
190 self._no_disk = data_file is None
191 if data_file is _DEFAULT_DATAFILE:
192 data_file = None
193
194 # Build our configuration from a number of sources.
195 self.config = read_coverage_config(
196 config_file=config_file,
197 data_file=data_file, cover_pylib=cover_pylib, timid=timid,
198 branch=branch, parallel=bool_or_none(data_suffix),
199 source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug,
200 report_omit=omit, report_include=include,
201 concurrency=concurrency, context=context,
202 )
203
204 # This is injectable by tests.
205 self._debug_file = None
206
207 self._auto_load = self._auto_save = auto_data
208 self._data_suffix_specified = data_suffix
209
210 # Is it ok for no data to be collected?
211 self._warn_no_data = True
212 self._warn_unimported_source = True
213 self._warn_preimported_source = check_preimported
214 self._no_warn_slugs = None
215
216 # A record of all the warnings that have been issued.
217 self._warnings = []
218
219 # Other instance attributes, set later.
220 self._data = self._collector = None
221 self._plugins = None
222 self._inorout = None
223 self._data_suffix = self._run_suffix = None
224 self._exclude_re = None
225 self._debug = None
226 self._file_mapper = None
227
228 # State machine variables:
229 # Have we initialized everything?
230 self._inited = False
231 self._inited_for_start = False
232 # Have we started collecting and not stopped it?
233 self._started = False
234 # Should we write the debug output?
235 self._should_write_debug = True
236
237 # If we have sub-process measurement happening automatically, then we
238 # want any explicit creation of a Coverage object to mean, this process
239 # is already coverage-aware, so don't auto-measure it. By now, the
240 # auto-creation of a Coverage object has already happened. But we can
241 # find it and tell it not to save its data.
242 if not env.METACOV:
243 _prevent_sub_process_measurement()
244
245 def _init(self):
246 """Set all the initial state.
247
248 This is called by the public methods to initialize state. This lets us
249 construct a :class:`Coverage` object, then tweak its state before this
250 function is called.
251
252 """
253 if self._inited:
254 return
255
256 self._inited = True
257
258 # Create and configure the debugging controller. COVERAGE_DEBUG_FILE
259 # is an environment variable, the name of a file to append debug logs
260 # to.
261 self._debug = DebugControl(self.config.debug, self._debug_file)
262
263 if "multiprocessing" in (self.config.concurrency or ()):
264 # Multi-processing uses parallel for the subprocesses, so also use
265 # it for the main process.
266 self.config.parallel = True
267
268 # _exclude_re is a dict that maps exclusion list names to compiled regexes.
269 self._exclude_re = {}
270
271 set_relative_directory()
272 self._file_mapper = relative_filename if self.config.relative_files else abs_file
273
274 # Load plugins
275 self._plugins = Plugins.load_plugins(self.config.plugins, self.config, self._debug)
276
277 # Run configuring plugins.
278 for plugin in self._plugins.configurers:
279 # We need an object with set_option and get_option. Either self or
280 # self.config will do. Choosing randomly stops people from doing
281 # other things with those objects, against the public API. Yes,
282 # this is a bit childish. :)
283 plugin.configure([self, self.config][int(time.time()) % 2])
284
285 def _post_init(self):
286 """Stuff to do after everything is initialized."""
287 if self._should_write_debug:
288 self._should_write_debug = False
289 self._write_startup_debug()
290
291 # '[run] _crash' will raise an exception if the value is close by in
292 # the call stack, for testing error handling.
293 if self.config._crash and self.config._crash in short_stack(limit=4):
294 raise Exception("Crashing because called by {}".format(self.config._crash))
295
296 def _write_startup_debug(self):
297 """Write out debug info at startup if needed."""
298 wrote_any = False
299 with self._debug.without_callers():
300 if self._debug.should('config'):
301 config_info = sorted(self.config.__dict__.items())
302 config_info = [(k, v) for k, v in config_info if not k.startswith('_')]
303 write_formatted_info(self._debug, "config", config_info)
304 wrote_any = True
305
306 if self._debug.should('sys'):
307 write_formatted_info(self._debug, "sys", self.sys_info())
308 for plugin in self._plugins:
309 header = "sys: " + plugin._coverage_plugin_name
310 info = plugin.sys_info()
311 write_formatted_info(self._debug, header, info)
312 wrote_any = True
313
314 if wrote_any:
315 write_formatted_info(self._debug, "end", ())
316
317 def _should_trace(self, filename, frame):
318 """Decide whether to trace execution in `filename`.
319
320 Calls `_should_trace_internal`, and returns the FileDisposition.
321
322 """
323 disp = self._inorout.should_trace(filename, frame)
324 if self._debug.should('trace'):
325 self._debug.write(disposition_debug_msg(disp))
326 return disp
327
328 def _check_include_omit_etc(self, filename, frame):
329 """Check a file name against the include/omit/etc, rules, verbosely.
330
331 Returns a boolean: True if the file should be traced, False if not.
332
333 """
334 reason = self._inorout.check_include_omit_etc(filename, frame)
335 if self._debug.should('trace'):
336 if not reason:
337 msg = "Including %r" % (filename,)
338 else:
339 msg = "Not including %r: %s" % (filename, reason)
340 self._debug.write(msg)
341
342 return not reason
343
344 def _warn(self, msg, slug=None, once=False):
345 """Use `msg` as a warning.
346
347 For warning suppression, use `slug` as the shorthand.
348
349 If `once` is true, only show this warning once (determined by the
350 slug.)
351
352 """
353 if self._no_warn_slugs is None:
354 self._no_warn_slugs = list(self.config.disable_warnings)
355
356 if slug in self._no_warn_slugs:
357 # Don't issue the warning
358 return
359
360 self._warnings.append(msg)
361 if slug:
362 msg = "%s (%s)" % (msg, slug)
363 if self._debug.should('pid'):
364 msg = "[%d] %s" % (os.getpid(), msg)
365 sys.stderr.write("Coverage.py warning: %s\n" % msg)
366
367 if once:
368 self._no_warn_slugs.append(slug)
369
370 def get_option(self, option_name):
371 """Get an option from the configuration.
372
373 `option_name` is a colon-separated string indicating the section and
374 option name. For example, the ``branch`` option in the ``[run]``
375 section of the config file would be indicated with `"run:branch"`.
376
377 Returns the value of the option. The type depends on the option
378 selected.
379
380 As a special case, an `option_name` of ``"paths"`` will return an
381 OrderedDict with the entire ``[paths]`` section value.
382
383 .. versionadded:: 4.0
384
385 """
386 return self.config.get_option(option_name)
387
388 def set_option(self, option_name, value):
389 """Set an option in the configuration.
390
391 `option_name` is a colon-separated string indicating the section and
392 option name. For example, the ``branch`` option in the ``[run]``
393 section of the config file would be indicated with ``"run:branch"``.
394
395 `value` is the new value for the option. This should be an
396 appropriate Python value. For example, use True for booleans, not the
397 string ``"True"``.
398
399 As an example, calling::
400
401 cov.set_option("run:branch", True)
402
403 has the same effect as this configuration file::
404
405 [run]
406 branch = True
407
408 As a special case, an `option_name` of ``"paths"`` will replace the
409 entire ``[paths]`` section. The value should be an OrderedDict.
410
411 .. versionadded:: 4.0
412
413 """
414 self.config.set_option(option_name, value)
415
416 def load(self):
417 """Load previously-collected coverage data from the data file."""
418 self._init()
419 if self._collector:
420 self._collector.reset()
421 should_skip = self.config.parallel and not os.path.exists(self.config.data_file)
422 if not should_skip:
423 self._init_data(suffix=None)
424 self._post_init()
425 if not should_skip:
426 self._data.read()
427
428 def _init_for_start(self):
429 """Initialization for start()"""
430 # Construct the collector.
431 concurrency = self.config.concurrency or ()
432 if "multiprocessing" in concurrency:
433 if not patch_multiprocessing:
434 raise CoverageException( # pragma: only jython
435 "multiprocessing is not supported on this Python"
436 )
437 patch_multiprocessing(rcfile=self.config.config_file)
438
439 dycon = self.config.dynamic_context
440 if not dycon or dycon == "none":
441 context_switchers = []
442 elif dycon == "test_function":
443 context_switchers = [should_start_context_test_function]
444 else:
445 raise CoverageException(
446 "Don't understand dynamic_context setting: {!r}".format(dycon)
447 )
448
449 context_switchers.extend(
450 plugin.dynamic_context for plugin in self._plugins.context_switchers
451 )
452
453 should_start_context = combine_context_switchers(context_switchers)
454
455 self._collector = Collector(
456 should_trace=self._should_trace,
457 check_include=self._check_include_omit_etc,
458 should_start_context=should_start_context,
459 file_mapper=self._file_mapper,
460 timid=self.config.timid,
461 branch=self.config.branch,
462 warn=self._warn,
463 concurrency=concurrency,
464 )
465
466 suffix = self._data_suffix_specified
467 if suffix or self.config.parallel:
468 if not isinstance(suffix, string_class):
469 # if data_suffix=True, use .machinename.pid.random
470 suffix = True
471 else:
472 suffix = None
473
474 self._init_data(suffix)
475
476 self._collector.use_data(self._data, self.config.context)
477
478 # Early warning if we aren't going to be able to support plugins.
479 if self._plugins.file_tracers and not self._collector.supports_plugins:
480 self._warn(
481 "Plugin file tracers (%s) aren't supported with %s" % (
482 ", ".join(
483 plugin._coverage_plugin_name
484 for plugin in self._plugins.file_tracers
485 ),
486 self._collector.tracer_name(),
487 )
488 )
489 for plugin in self._plugins.file_tracers:
490 plugin._coverage_enabled = False
491
492 # Create the file classifying substructure.
493 self._inorout = InOrOut(
494 warn=self._warn,
495 debug=(self._debug if self._debug.should('trace') else None),
496 )
497 self._inorout.configure(self.config)
498 self._inorout.plugins = self._plugins
499 self._inorout.disp_class = self._collector.file_disposition_class
500
501 # It's useful to write debug info after initing for start.
502 self._should_write_debug = True
503
504 atexit.register(self._atexit)
505
506 def _init_data(self, suffix):
507 """Create a data file if we don't have one yet."""
508 if self._data is None:
509 # Create the data file. We do this at construction time so that the
510 # data file will be written into the directory where the process
511 # started rather than wherever the process eventually chdir'd to.
512 ensure_dir_for_file(self.config.data_file)
513 self._data = CoverageData(
514 basename=self.config.data_file,
515 suffix=suffix,
516 warn=self._warn,
517 debug=self._debug,
518 no_disk=self._no_disk,
519 )
520
521 def start(self):
522 """Start measuring code coverage.
523
524 Coverage measurement only occurs in functions called after
525 :meth:`start` is invoked. Statements in the same scope as
526 :meth:`start` won't be measured.
527
528 Once you invoke :meth:`start`, you must also call :meth:`stop`
529 eventually, or your process might not shut down cleanly.
530
531 """
532 self._init()
533 if not self._inited_for_start:
534 self._inited_for_start = True
535 self._init_for_start()
536 self._post_init()
537
538 # Issue warnings for possible problems.
539 self._inorout.warn_conflicting_settings()
540
541 # See if we think some code that would eventually be measured has
542 # already been imported.
543 if self._warn_preimported_source:
544 self._inorout.warn_already_imported_files()
545
546 if self._auto_load:
547 self.load()
548
549 self._collector.start()
550 self._started = True
551 self._instances.append(self)
552
553 def stop(self):
554 """Stop measuring code coverage."""
555 if self._instances:
556 if self._instances[-1] is self:
557 self._instances.pop()
558 if self._started:
559 self._collector.stop()
560 self._started = False
561
562 def _atexit(self):
563 """Clean up on process shutdown."""
564 if self._debug.should("process"):
565 self._debug.write("atexit: pid: {}, instance: {!r}".format(os.getpid(), self))
566 if self._started:
567 self.stop()
568 if self._auto_save:
569 self.save()
570
571 def erase(self):
572 """Erase previously collected coverage data.
573
574 This removes the in-memory data collected in this session as well as
575 discarding the data file.
576
577 """
578 self._init()
579 self._post_init()
580 if self._collector:
581 self._collector.reset()
582 self._init_data(suffix=None)
583 self._data.erase(parallel=self.config.parallel)
584 self._data = None
585 self._inited_for_start = False
586
587 def switch_context(self, new_context):
588 """Switch to a new dynamic context.
589
590 `new_context` is a string to use as the :ref:`dynamic context
591 <dynamic_contexts>` label for collected data. If a :ref:`static
592 context <static_contexts>` is in use, the static and dynamic context
593 labels will be joined together with a pipe character.
594
595 Coverage collection must be started already.
596
597 .. versionadded:: 5.0
598
599 """
600 if not self._started: # pragma: part started
601 raise CoverageException(
602 "Cannot switch context, coverage is not started"
603 )
604
605 if self._collector.should_start_context:
606 self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
607
608 self._collector.switch_context(new_context)
609
610 def clear_exclude(self, which='exclude'):
611 """Clear the exclude list."""
612 self._init()
613 setattr(self.config, which + "_list", [])
614 self._exclude_regex_stale()
615
616 def exclude(self, regex, which='exclude'):
617 """Exclude source lines from execution consideration.
618
619 A number of lists of regular expressions are maintained. Each list
620 selects lines that are treated differently during reporting.
621
622 `which` determines which list is modified. The "exclude" list selects
623 lines that are not considered executable at all. The "partial" list
624 indicates lines with branches that are not taken.
625
626 `regex` is a regular expression. The regex is added to the specified
627 list. If any of the regexes in the list is found in a line, the line
628 is marked for special treatment during reporting.
629
630 """
631 self._init()
632 excl_list = getattr(self.config, which + "_list")
633 excl_list.append(regex)
634 self._exclude_regex_stale()
635
636 def _exclude_regex_stale(self):
637 """Drop all the compiled exclusion regexes, a list was modified."""
638 self._exclude_re.clear()
639
640 def _exclude_regex(self, which):
641 """Return a compiled regex for the given exclusion list."""
642 if which not in self._exclude_re:
643 excl_list = getattr(self.config, which + "_list")
644 self._exclude_re[which] = join_regex(excl_list)
645 return self._exclude_re[which]
646
647 def get_exclude_list(self, which='exclude'):
648 """Return a list of excluded regex patterns.
649
650 `which` indicates which list is desired. See :meth:`exclude` for the
651 lists that are available, and their meaning.
652
653 """
654 self._init()
655 return getattr(self.config, which + "_list")
656
657 def save(self):
658 """Save the collected coverage data to the data file."""
659 data = self.get_data()
660 data.write()
661
662 def combine(self, data_paths=None, strict=False):
663 """Combine together a number of similarly-named coverage data files.
664
665 All coverage data files whose name starts with `data_file` (from the
666 coverage() constructor) will be read, and combined together into the
667 current measurements.
668
669 `data_paths` is a list of files or directories from which data should
670 be combined. If no list is passed, then the data files from the
671 directory indicated by the current data file (probably the current
672 directory) will be combined.
673
674 If `strict` is true, then it is an error to attempt to combine when
675 there are no data files to combine.
676
677 .. versionadded:: 4.0
678 The `data_paths` parameter.
679
680 .. versionadded:: 4.3
681 The `strict` parameter.
682
683 """
684 self._init()
685 self._init_data(suffix=None)
686 self._post_init()
687 self.get_data()
688
689 aliases = None
690 if self.config.paths:
691 aliases = PathAliases()
692 for paths in self.config.paths.values():
693 result = paths[0]
694 for pattern in paths[1:]:
695 aliases.add(pattern, result)
696
697 combine_parallel_data(self._data, aliases=aliases, data_paths=data_paths, strict=strict)
698
699 def get_data(self):
700 """Get the collected data.
701
702 Also warn about various problems collecting data.
703
704 Returns a :class:`coverage.CoverageData`, the collected coverage data.
705
706 .. versionadded:: 4.0
707
708 """
709 self._init()
710 self._init_data(suffix=None)
711 self._post_init()
712
713 for plugin in self._plugins:
714 if not plugin._coverage_enabled:
715 self._collector.plugin_was_disabled(plugin)
716
717 if self._collector and self._collector.flush_data():
718 self._post_save_work()
719
720 return self._data
721
722 def _post_save_work(self):
723 """After saving data, look for warnings, post-work, etc.
724
725 Warn about things that should have happened but didn't.
726 Look for unexecuted files.
727
728 """
729 # If there are still entries in the source_pkgs_unmatched list,
730 # then we never encountered those packages.
731 if self._warn_unimported_source:
732 self._inorout.warn_unimported_source()
733
734 # Find out if we got any data.
735 if not self._data and self._warn_no_data:
736 self._warn("No data was collected.", slug="no-data-collected")
737
738 # Touch all the files that could have executed, so that we can
739 # mark completely unexecuted files as 0% covered.
740 if self._data is not None:
741 file_paths = collections.defaultdict(list)
742 for file_path, plugin_name in self._inorout.find_possibly_unexecuted_files():
743 file_path = self._file_mapper(file_path)
744 file_paths[plugin_name].append(file_path)
745 for plugin_name, paths in file_paths.items():
746 self._data.touch_files(paths, plugin_name)
747
748 if self.config.note:
749 self._warn("The '[run] note' setting is no longer supported.")
750
751 # Backward compatibility with version 1.
752 def analysis(self, morf):
753 """Like `analysis2` but doesn't return excluded line numbers."""
754 f, s, _, m, mf = self.analysis2(morf)
755 return f, s, m, mf
756
757 def analysis2(self, morf):
758 """Analyze a module.
759
760 `morf` is a module or a file name. It will be analyzed to determine
761 its coverage statistics. The return value is a 5-tuple:
762
763 * The file name for the module.
764 * A list of line numbers of executable statements.
765 * A list of line numbers of excluded statements.
766 * A list of line numbers of statements not run (missing from
767 execution).
768 * A readable formatted string of the missing line numbers.
769
770 The analysis uses the source file itself and the current measured
771 coverage data.
772
773 """
774 analysis = self._analyze(morf)
775 return (
776 analysis.filename,
777 sorted(analysis.statements),
778 sorted(analysis.excluded),
779 sorted(analysis.missing),
780 analysis.missing_formatted(),
781 )
782
783 def _analyze(self, it):
784 """Analyze a single morf or code unit.
785
786 Returns an `Analysis` object.
787
788 """
789 # All reporting comes through here, so do reporting initialization.
790 self._init()
791 Numbers.set_precision(self.config.precision)
792 self._post_init()
793
794 data = self.get_data()
795 if not isinstance(it, FileReporter):
796 it = self._get_file_reporter(it)
797
798 return Analysis(data, it, self._file_mapper)
799
800 def _get_file_reporter(self, morf):
801 """Get a FileReporter for a module or file name."""
802 plugin = None
803 file_reporter = "python"
804
805 if isinstance(morf, string_class):
806 mapped_morf = self._file_mapper(morf)
807 plugin_name = self._data.file_tracer(mapped_morf)
808 if plugin_name:
809 plugin = self._plugins.get(plugin_name)
810
811 if plugin:
812 file_reporter = plugin.file_reporter(mapped_morf)
813 if file_reporter is None:
814 raise CoverageException(
815 "Plugin %r did not provide a file reporter for %r." % (
816 plugin._coverage_plugin_name, morf
817 )
818 )
819
820 if file_reporter == "python":
821 file_reporter = PythonFileReporter(morf, self)
822
823 return file_reporter
824
825 def _get_file_reporters(self, morfs=None):
826 """Get a list of FileReporters for a list of modules or file names.
827
828 For each module or file name in `morfs`, find a FileReporter. Return
829 the list of FileReporters.
830
831 If `morfs` is a single module or file name, this returns a list of one
832 FileReporter. If `morfs` is empty or None, then the list of all files
833 measured is used to find the FileReporters.
834
835 """
836 if not morfs:
837 morfs = self._data.measured_files()
838
839 # Be sure we have a collection.
840 if not isinstance(morfs, (list, tuple, set)):
841 morfs = [morfs]
842
843 file_reporters = [self._get_file_reporter(morf) for morf in morfs]
844 return file_reporters
845
846 def report(
847 self, morfs=None, show_missing=None, ignore_errors=None,
848 file=None, omit=None, include=None, skip_covered=None,
849 contexts=None, skip_empty=None, precision=None, sort=None
850 ):
851 """Write a textual summary report to `file`.
852
853 Each module in `morfs` is listed, with counts of statements, executed
854 statements, missing statements, and a list of lines missed.
855
856 If `show_missing` is true, then details of which lines or branches are
857 missing will be included in the report. If `ignore_errors` is true,
858 then a failure while reporting a single file will not stop the entire
859 report.
860
861 `file` is a file-like object, suitable for writing.
862
863 `include` is a list of file name patterns. Files that match will be
864 included in the report. Files matching `omit` will not be included in
865 the report.
866
867 If `skip_covered` is true, don't report on files with 100% coverage.
868
869 If `skip_empty` is true, don't report on empty files (those that have
870 no statements).
871
872 `contexts` is a list of regular expressions. Only data from
873 :ref:`dynamic contexts <dynamic_contexts>` that match one of those
874 expressions (using :func:`re.search <python:re.search>`) will be
875 included in the report.
876
877 `precision` is the number of digits to display after the decimal
878 point for percentages.
879
880 All of the arguments default to the settings read from the
881 :ref:`configuration file <config>`.
882
883 Returns a float, the total percentage covered.
884
885 .. versionadded:: 4.0
886 The `skip_covered` parameter.
887
888 .. versionadded:: 5.0
889 The `contexts` and `skip_empty` parameters.
890
891 .. versionadded:: 5.2
892 The `precision` parameter.
893
894 """
895 with override_config(
896 self,
897 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
898 show_missing=show_missing, skip_covered=skip_covered,
899 report_contexts=contexts, skip_empty=skip_empty, precision=precision,
900 sort=sort
901 ):
902 reporter = SummaryReporter(self)
903 return reporter.report(morfs, outfile=file)
904
905 def annotate(
906 self, morfs=None, directory=None, ignore_errors=None,
907 omit=None, include=None, contexts=None,
908 ):
909 """Annotate a list of modules.
910
911 Each module in `morfs` is annotated. The source is written to a new
912 file, named with a ",cover" suffix, with each line prefixed with a
913 marker to indicate the coverage of the line. Covered lines have ">",
914 excluded lines have "-", and missing lines have "!".
915
916 See :meth:`report` for other arguments.
917
918 """
919 with override_config(self,
920 ignore_errors=ignore_errors, report_omit=omit,
921 report_include=include, report_contexts=contexts,
922 ):
923 reporter = AnnotateReporter(self)
924 reporter.report(morfs, directory=directory)
925
926 def html_report(
927 self, morfs=None, directory=None, ignore_errors=None,
928 omit=None, include=None, extra_css=None, title=None,
929 skip_covered=None, show_contexts=None, contexts=None,
930 skip_empty=None, precision=None,
931 ):
932 """Generate an HTML report.
933
934 The HTML is written to `directory`. The file "index.html" is the
935 overview starting point, with links to more detailed pages for
936 individual modules.
937
938 `extra_css` is a path to a file of other CSS to apply on the page.
939 It will be copied into the HTML directory.
940
941 `title` is a text string (not HTML) to use as the title of the HTML
942 report.
943
944 See :meth:`report` for other arguments.
945
946 Returns a float, the total percentage covered.
947
948 .. note::
949 The HTML report files are generated incrementally based on the
950 source files and coverage results. If you modify the report files,
951 the changes will not be considered. You should be careful about
952 changing the files in the report folder.
953
954 """
955 with override_config(self,
956 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
957 html_dir=directory, extra_css=extra_css, html_title=title,
958 skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts,
959 skip_empty=skip_empty, precision=precision,
960 ):
961 reporter = HtmlReporter(self)
962 return reporter.report(morfs)
963
964 def xml_report(
965 self, morfs=None, outfile=None, ignore_errors=None,
966 omit=None, include=None, contexts=None, skip_empty=None,
967 ):
968 """Generate an XML report of coverage results.
969
970 The report is compatible with Cobertura reports.
971
972 Each module in `morfs` is included in the report. `outfile` is the
973 path to write the file to, "-" will write to stdout.
974
975 See :meth:`report` for other arguments.
976
977 Returns a float, the total percentage covered.
978
979 """
980 with override_config(self,
981 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
982 xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty,
983 ):
984 return render_report(self.config.xml_output, XmlReporter(self), morfs)
985
986 def json_report(
987 self, morfs=None, outfile=None, ignore_errors=None,
988 omit=None, include=None, contexts=None, pretty_print=None,
989 show_contexts=None
990 ):
991 """Generate a JSON report of coverage results.
992
993 Each module in `morfs` is included in the report. `outfile` is the
994 path to write the file to, "-" will write to stdout.
995
996 See :meth:`report` for other arguments.
997
998 Returns a float, the total percentage covered.
999
1000 .. versionadded:: 5.0
1001
1002 """
1003 with override_config(self,
1004 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1005 json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print,
1006 json_show_contexts=show_contexts
1007 ):
1008 return render_report(self.config.json_output, JsonReporter(self), morfs)
1009
1010 def sys_info(self):
1011 """Return a list of (key, value) pairs showing internal information."""
1012
1013 import coverage as covmod
1014
1015 self._init()
1016 self._post_init()
1017
1018 def plugin_info(plugins):
1019 """Make an entry for the sys_info from a list of plug-ins."""
1020 entries = []
1021 for plugin in plugins:
1022 entry = plugin._coverage_plugin_name
1023 if not plugin._coverage_enabled:
1024 entry += " (disabled)"
1025 entries.append(entry)
1026 return entries
1027
1028 info = [
1029 ('version', covmod.__version__),
1030 ('coverage', covmod.__file__),
1031 ('tracer', self._collector.tracer_name() if self._collector else "-none-"),
1032 ('CTracer', 'available' if CTracer else "unavailable"),
1033 ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)),
1034 ('plugins.configurers', plugin_info(self._plugins.configurers)),
1035 ('plugins.context_switchers', plugin_info(self._plugins.context_switchers)),
1036 ('configs_attempted', self.config.attempted_config_files),
1037 ('configs_read', self.config.config_files_read),
1038 ('config_file', self.config.config_file),
1039 ('config_contents',
1040 repr(self.config._config_contents)
1041 if self.config._config_contents
1042 else '-none-'
1043 ),
1044 ('data_file', self._data.data_filename() if self._data is not None else "-none-"),
1045 ('python', sys.version.replace('\n', '')),
1046 ('platform', platform.platform()),
1047 ('implementation', platform.python_implementation()),
1048 ('executable', sys.executable),
1049 ('def_encoding', sys.getdefaultencoding()),
1050 ('fs_encoding', sys.getfilesystemencoding()),
1051 ('pid', os.getpid()),
1052 ('cwd', os.getcwd()),
1053 ('path', sys.path),
1054 ('environment', sorted(
1055 ("%s = %s" % (k, v))
1056 for k, v in iitems(os.environ)
1057 if any(slug in k for slug in ("COV", "PY"))
1058 )),
1059 ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))),
1060 ]
1061
1062 if self._inorout:
1063 info.extend(self._inorout.sys_info())
1064
1065 info.extend(CoverageData.sys_info())
1066
1067 return info
1068
1069
1070 # Mega debugging...
1071 # $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage.
1072 if int(os.environ.get("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging
1073 from coverage.debug import decorate_methods, show_calls
1074
1075 Coverage = decorate_methods(show_calls(show_args=True), butnot=['get_data'])(Coverage)
1076
1077
1078 def process_startup():
1079 """Call this at Python start-up to perhaps measure coverage.
1080
1081 If the environment variable COVERAGE_PROCESS_START is defined, coverage
1082 measurement is started. The value of the variable is the config file
1083 to use.
1084
1085 There are two ways to configure your Python installation to invoke this
1086 function when Python starts:
1087
1088 #. Create or append to sitecustomize.py to add these lines::
1089
1090 import coverage
1091 coverage.process_startup()
1092
1093 #. Create a .pth file in your Python installation containing::
1094
1095 import coverage; coverage.process_startup()
1096
1097 Returns the :class:`Coverage` instance that was started, or None if it was
1098 not started by this call.
1099
1100 """
1101 cps = os.environ.get("COVERAGE_PROCESS_START")
1102 if not cps:
1103 # No request for coverage, nothing to do.
1104 return None
1105
1106 # This function can be called more than once in a process. This happens
1107 # because some virtualenv configurations make the same directory visible
1108 # twice in sys.path. This means that the .pth file will be found twice,
1109 # and executed twice, executing this function twice. We set a global
1110 # flag (an attribute on this function) to indicate that coverage.py has
1111 # already been started, so we can avoid doing it twice.
1112 #
1113 # https://github.com/nedbat/coveragepy/issues/340 has more details.
1114
1115 if hasattr(process_startup, "coverage"):
1116 # We've annotated this function before, so we must have already
1117 # started coverage.py in this process. Nothing to do.
1118 return None
1119
1120 cov = Coverage(config_file=cps)
1121 process_startup.coverage = cov
1122 cov._warn_no_data = False
1123 cov._warn_unimported_source = False
1124 cov._warn_preimported_source = False
1125 cov._auto_save = True
1126 cov.start()
1127
1128 return cov
1129
1130
1131 def _prevent_sub_process_measurement():
1132 """Stop any subprocess auto-measurement from writing data."""
1133 auto_created_coverage = getattr(process_startup, "coverage", None)
1134 if auto_created_coverage is not None:
1135 auto_created_coverage._auto_save = False

eric ide

mercurial