eric6/DebugClients/Python/coverage/control.py

changeset 6942
2602857055c5
parent 6649
f1b3a73831c9
child 7427
362cd1b6f81a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3
4 """Core control stuff for coverage.py."""
5
6
7 import atexit
8 import inspect
9 import itertools
10 import os
11 import platform
12 import re
13 import sys
14 import time
15 import traceback
16
17 from coverage import env
18 from coverage.annotate import AnnotateReporter
19 from coverage.backward import string_class, iitems
20 from coverage.collector import Collector
21 from coverage.config import read_coverage_config
22 from coverage.data import CoverageData, CoverageDataFiles
23 from coverage.debug import DebugControl, write_formatted_info
24 from coverage.files import TreeMatcher, FnmatchMatcher
25 from coverage.files import PathAliases, find_python_files, prep_patterns
26 from coverage.files import canonical_filename, set_relative_directory
27 from coverage.files import ModuleMatcher, abs_file
28 from coverage.html import HtmlReporter
29 from coverage.misc import CoverageException, bool_or_none, join_regex
30 from coverage.misc import file_be_gone, isolate_module
31 from coverage.plugin import FileReporter
32 from coverage.plugin_support import Plugins
33 from coverage.python import PythonFileReporter, source_for_file
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 # Pypy has some unusual stuff in the "stdlib". Consider those locations
47 # when deciding where the stdlib is. These modules are not used for anything,
48 # they are modules importable from the pypy lib directories, so that we can
49 # find those directories.
50 _structseq = _pypy_irc_topic = None
51 if env.PYPY:
52 try:
53 import _structseq
54 except ImportError:
55 pass
56
57 try:
58 import _pypy_irc_topic
59 except ImportError:
60 pass
61
62
63 class Coverage(object):
64 """Programmatic access to coverage.py.
65
66 To use::
67
68 from coverage import Coverage
69
70 cov = Coverage()
71 cov.start()
72 #.. call your code ..
73 cov.stop()
74 cov.html_report(directory='covhtml')
75
76 """
77 def __init__(
78 self, data_file=None, data_suffix=None, cover_pylib=None,
79 auto_data=False, timid=None, branch=None, config_file=True,
80 source=None, omit=None, include=None, debug=None,
81 concurrency=None,
82 ):
83 """
84 `data_file` is the base name of the data file to use, defaulting to
85 ".coverage". `data_suffix` is appended (with a dot) to `data_file` to
86 create the final file name. If `data_suffix` is simply True, then a
87 suffix is created with the machine and process identity included.
88
89 `cover_pylib` is a boolean determining whether Python code installed
90 with the Python interpreter is measured. This includes the Python
91 standard library and any packages installed with the interpreter.
92
93 If `auto_data` is true, then any existing data file will be read when
94 coverage measurement starts, and data will be saved automatically when
95 measurement stops.
96
97 If `timid` is true, then a slower and simpler trace function will be
98 used. This is important for some environments where manipulation of
99 tracing functions breaks the faster trace function.
100
101 If `branch` is true, then branch coverage will be measured in addition
102 to the usual statement coverage.
103
104 `config_file` determines what configuration file to read:
105
106 * If it is ".coveragerc", it is interpreted as if it were True,
107 for backward compatibility.
108
109 * If it is a string, it is the name of the file to read. If the
110 file can't be read, it is an error.
111
112 * If it is True, then a few standard files names are tried
113 (".coveragerc", "setup.cfg", "tox.ini"). It is not an error for
114 these files to not be found.
115
116 * If it is False, then no configuration file is read.
117
118 `source` is a list of file paths or package names. Only code located
119 in the trees indicated by the file paths or package names will be
120 measured.
121
122 `include` and `omit` are lists of file name patterns. Files that match
123 `include` will be measured, files that match `omit` will not. Each
124 will also accept a single string argument.
125
126 `debug` is a list of strings indicating what debugging information is
127 desired.
128
129 `concurrency` is a string indicating the concurrency library being used
130 in the measured code. Without this, coverage.py will get incorrect
131 results if these libraries are in use. Valid strings are "greenlet",
132 "eventlet", "gevent", "multiprocessing", or "thread" (the default).
133 This can also be a list of these strings.
134
135 .. versionadded:: 4.0
136 The `concurrency` parameter.
137
138 .. versionadded:: 4.2
139 The `concurrency` parameter can now be a list of strings.
140
141 """
142 # Build our configuration from a number of sources.
143 self.config_file, self.config = read_coverage_config(
144 config_file=config_file,
145 data_file=data_file, cover_pylib=cover_pylib, timid=timid,
146 branch=branch, parallel=bool_or_none(data_suffix),
147 source=source, run_omit=omit, run_include=include, debug=debug,
148 report_omit=omit, report_include=include,
149 concurrency=concurrency,
150 )
151
152 # This is injectable by tests.
153 self._debug_file = None
154
155 self._auto_load = self._auto_save = auto_data
156 self._data_suffix = data_suffix
157
158 # The matchers for _should_trace.
159 self.source_match = None
160 self.source_pkgs_match = None
161 self.pylib_match = self.cover_match = None
162 self.include_match = self.omit_match = None
163
164 # Is it ok for no data to be collected?
165 self._warn_no_data = True
166 self._warn_unimported_source = True
167
168 # A record of all the warnings that have been issued.
169 self._warnings = []
170
171 # Other instance attributes, set later.
172 self.omit = self.include = self.source = None
173 self.source_pkgs_unmatched = None
174 self.source_pkgs = None
175 self.data = self.data_files = self.collector = None
176 self.plugins = None
177 self.pylib_paths = self.cover_paths = None
178 self.data_suffix = self.run_suffix = None
179 self._exclude_re = None
180 self.debug = None
181
182 # State machine variables:
183 # Have we initialized everything?
184 self._inited = False
185 # Have we started collecting and not stopped it?
186 self._started = False
187
188 # If we have sub-process measurement happening automatically, then we
189 # want any explicit creation of a Coverage object to mean, this process
190 # is already coverage-aware, so don't auto-measure it. By now, the
191 # auto-creation of a Coverage object has already happened. But we can
192 # find it and tell it not to save its data.
193 if not env.METACOV:
194 _prevent_sub_process_measurement()
195
196 def _init(self):
197 """Set all the initial state.
198
199 This is called by the public methods to initialize state. This lets us
200 construct a :class:`Coverage` object, then tweak its state before this
201 function is called.
202
203 """
204 if self._inited:
205 return
206
207 self._inited = True
208
209 # Create and configure the debugging controller. COVERAGE_DEBUG_FILE
210 # is an environment variable, the name of a file to append debug logs
211 # to.
212 if self._debug_file is None:
213 debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE")
214 if debug_file_name:
215 self._debug_file = open(debug_file_name, "a")
216 else:
217 self._debug_file = sys.stderr
218 self.debug = DebugControl(self.config.debug, self._debug_file)
219
220 # _exclude_re is a dict that maps exclusion list names to compiled regexes.
221 self._exclude_re = {}
222
223 set_relative_directory()
224
225 # Load plugins
226 self.plugins = Plugins.load_plugins(self.config.plugins, self.config, self.debug)
227
228 # Run configuring plugins.
229 for plugin in self.plugins.configurers:
230 # We need an object with set_option and get_option. Either self or
231 # self.config will do. Choosing randomly stops people from doing
232 # other things with those objects, against the public API. Yes,
233 # this is a bit childish. :)
234 plugin.configure([self, self.config][int(time.time()) % 2])
235
236 # The source argument can be directories or package names.
237 self.source = []
238 self.source_pkgs = []
239 for src in self.config.source or []:
240 if os.path.isdir(src):
241 self.source.append(canonical_filename(src))
242 else:
243 self.source_pkgs.append(src)
244 self.source_pkgs_unmatched = self.source_pkgs[:]
245
246 self.omit = prep_patterns(self.config.run_omit)
247 self.include = prep_patterns(self.config.run_include)
248
249 concurrency = self.config.concurrency or []
250 if "multiprocessing" in concurrency:
251 if not patch_multiprocessing:
252 raise CoverageException( # pragma: only jython
253 "multiprocessing is not supported on this Python"
254 )
255 patch_multiprocessing(rcfile=self.config_file)
256 # Multi-processing uses parallel for the subprocesses, so also use
257 # it for the main process.
258 self.config.parallel = True
259
260 self.collector = Collector(
261 should_trace=self._should_trace,
262 check_include=self._check_include_omit_etc,
263 timid=self.config.timid,
264 branch=self.config.branch,
265 warn=self._warn,
266 concurrency=concurrency,
267 )
268
269 # Early warning if we aren't going to be able to support plugins.
270 if self.plugins.file_tracers and not self.collector.supports_plugins:
271 self._warn(
272 "Plugin file tracers (%s) aren't supported with %s" % (
273 ", ".join(
274 plugin._coverage_plugin_name
275 for plugin in self.plugins.file_tracers
276 ),
277 self.collector.tracer_name(),
278 )
279 )
280 for plugin in self.plugins.file_tracers:
281 plugin._coverage_enabled = False
282
283 # Suffixes are a bit tricky. We want to use the data suffix only when
284 # collecting data, not when combining data. So we save it as
285 # `self.run_suffix` now, and promote it to `self.data_suffix` if we
286 # find that we are collecting data later.
287 if self._data_suffix or self.config.parallel:
288 if not isinstance(self._data_suffix, string_class):
289 # if data_suffix=True, use .machinename.pid.random
290 self._data_suffix = True
291 else:
292 self._data_suffix = None
293 self.data_suffix = None
294 self.run_suffix = self._data_suffix
295
296 # Create the data file. We do this at construction time so that the
297 # data file will be written into the directory where the process
298 # started rather than wherever the process eventually chdir'd to.
299 self.data = CoverageData(debug=self.debug)
300 self.data_files = CoverageDataFiles(
301 basename=self.config.data_file, warn=self._warn, debug=self.debug,
302 )
303
304 # The directories for files considered "installed with the interpreter".
305 self.pylib_paths = set()
306 if not self.config.cover_pylib:
307 # Look at where some standard modules are located. That's the
308 # indication for "installed with the interpreter". In some
309 # environments (virtualenv, for example), these modules may be
310 # spread across a few locations. Look at all the candidate modules
311 # we've imported, and take all the different ones.
312 for m in (atexit, inspect, os, platform, _pypy_irc_topic, re, _structseq, traceback):
313 if m is not None and hasattr(m, "__file__"):
314 self.pylib_paths.add(self._canonical_path(m, directory=True))
315
316 if _structseq and not hasattr(_structseq, '__file__'):
317 # PyPy 2.4 has no __file__ in the builtin modules, but the code
318 # objects still have the file names. So dig into one to find
319 # the path to exclude.
320 structseq_new = _structseq.structseq_new
321 try:
322 structseq_file = structseq_new.func_code.co_filename
323 except AttributeError:
324 structseq_file = structseq_new.__code__.co_filename
325 self.pylib_paths.add(self._canonical_path(structseq_file))
326
327 # To avoid tracing the coverage.py code itself, we skip anything
328 # located where we are.
329 self.cover_paths = [self._canonical_path(__file__, directory=True)]
330 if env.TESTING:
331 # Don't include our own test code.
332 self.cover_paths.append(os.path.join(self.cover_paths[0], "tests"))
333
334 # When testing, we use PyContracts, which should be considered
335 # part of coverage.py, and it uses six. Exclude those directories
336 # just as we exclude ourselves.
337 import contracts
338 import six
339 for mod in [contracts, six]:
340 self.cover_paths.append(self._canonical_path(mod))
341
342 # Set the reporting precision.
343 Numbers.set_precision(self.config.precision)
344
345 atexit.register(self._atexit)
346
347 # Create the matchers we need for _should_trace
348 if self.source or self.source_pkgs:
349 self.source_match = TreeMatcher(self.source)
350 self.source_pkgs_match = ModuleMatcher(self.source_pkgs)
351 else:
352 if self.cover_paths:
353 self.cover_match = TreeMatcher(self.cover_paths)
354 if self.pylib_paths:
355 self.pylib_match = TreeMatcher(self.pylib_paths)
356 if self.include:
357 self.include_match = FnmatchMatcher(self.include)
358 if self.omit:
359 self.omit_match = FnmatchMatcher(self.omit)
360
361 # The user may want to debug things, show info if desired.
362 self._write_startup_debug()
363
364 def _write_startup_debug(self):
365 """Write out debug info at startup if needed."""
366 wrote_any = False
367 with self.debug.without_callers():
368 if self.debug.should('config'):
369 config_info = sorted(self.config.__dict__.items())
370 write_formatted_info(self.debug, "config", config_info)
371 wrote_any = True
372
373 if self.debug.should('sys'):
374 write_formatted_info(self.debug, "sys", self.sys_info())
375 for plugin in self.plugins:
376 header = "sys: " + plugin._coverage_plugin_name
377 info = plugin.sys_info()
378 write_formatted_info(self.debug, header, info)
379 wrote_any = True
380
381 if wrote_any:
382 write_formatted_info(self.debug, "end", ())
383
384 def _canonical_path(self, morf, directory=False):
385 """Return the canonical path of the module or file `morf`.
386
387 If the module is a package, then return its directory. If it is a
388 module, then return its file, unless `directory` is True, in which
389 case return its enclosing directory.
390
391 """
392 morf_path = PythonFileReporter(morf, self).filename
393 if morf_path.endswith("__init__.py") or directory:
394 morf_path = os.path.split(morf_path)[0]
395 return morf_path
396
397 def _name_for_module(self, module_globals, filename):
398 """Get the name of the module for a set of globals and file name.
399
400 For configurability's sake, we allow __main__ modules to be matched by
401 their importable name.
402
403 If loaded via runpy (aka -m), we can usually recover the "original"
404 full dotted module name, otherwise, we resort to interpreting the
405 file name to get the module's name. In the case that the module name
406 can't be determined, None is returned.
407
408 """
409 if module_globals is None: # pragma: only ironpython
410 # IronPython doesn't provide globals: https://github.com/IronLanguages/main/issues/1296
411 module_globals = {}
412
413 dunder_name = module_globals.get('__name__', None)
414
415 if isinstance(dunder_name, str) and dunder_name != '__main__':
416 # This is the usual case: an imported module.
417 return dunder_name
418
419 loader = module_globals.get('__loader__', None)
420 for attrname in ('fullname', 'name'): # attribute renamed in py3.2
421 if hasattr(loader, attrname):
422 fullname = getattr(loader, attrname)
423 else:
424 continue
425
426 if isinstance(fullname, str) and fullname != '__main__':
427 # Module loaded via: runpy -m
428 return fullname
429
430 # Script as first argument to Python command line.
431 inspectedname = inspect.getmodulename(filename)
432 if inspectedname is not None:
433 return inspectedname
434 else:
435 return dunder_name
436
437 def _should_trace_internal(self, filename, frame):
438 """Decide whether to trace execution in `filename`, with a reason.
439
440 This function is called from the trace function. As each new file name
441 is encountered, this function determines whether it is traced or not.
442
443 Returns a FileDisposition object.
444
445 """
446 original_filename = filename
447 disp = _disposition_init(self.collector.file_disposition_class, filename)
448
449 def nope(disp, reason):
450 """Simple helper to make it easy to return NO."""
451 disp.trace = False
452 disp.reason = reason
453 return disp
454
455 # Compiled Python files have two file names: frame.f_code.co_filename is
456 # the file name at the time the .pyc was compiled. The second name is
457 # __file__, which is where the .pyc was actually loaded from. Since
458 # .pyc files can be moved after compilation (for example, by being
459 # installed), we look for __file__ in the frame and prefer it to the
460 # co_filename value.
461 dunder_file = frame.f_globals and frame.f_globals.get('__file__')
462 if dunder_file:
463 filename = source_for_file(dunder_file)
464 if original_filename and not original_filename.startswith('<'):
465 orig = os.path.basename(original_filename)
466 if orig != os.path.basename(filename):
467 # Files shouldn't be renamed when moved. This happens when
468 # exec'ing code. If it seems like something is wrong with
469 # the frame's file name, then just use the original.
470 filename = original_filename
471
472 if not filename:
473 # Empty string is pretty useless.
474 return nope(disp, "empty string isn't a file name")
475
476 if filename.startswith('memory:'):
477 return nope(disp, "memory isn't traceable")
478
479 if filename.startswith('<'):
480 # Lots of non-file execution is represented with artificial
481 # file names like "<string>", "<doctest readme.txt[0]>", or
482 # "<exec_function>". Don't ever trace these executions, since we
483 # can't do anything with the data later anyway.
484 return nope(disp, "not a real file name")
485
486 # pyexpat does a dumb thing, calling the trace function explicitly from
487 # C code with a C file name.
488 if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename):
489 return nope(disp, "pyexpat lies about itself")
490
491 # Jython reports the .class file to the tracer, use the source file.
492 if filename.endswith("$py.class"):
493 filename = filename[:-9] + ".py"
494
495 canonical = canonical_filename(filename)
496 disp.canonical_filename = canonical
497
498 # Try the plugins, see if they have an opinion about the file.
499 plugin = None
500 for plugin in self.plugins.file_tracers:
501 if not plugin._coverage_enabled:
502 continue
503
504 try:
505 file_tracer = plugin.file_tracer(canonical)
506 if file_tracer is not None:
507 file_tracer._coverage_plugin = plugin
508 disp.trace = True
509 disp.file_tracer = file_tracer
510 if file_tracer.has_dynamic_source_filename():
511 disp.has_dynamic_filename = True
512 else:
513 disp.source_filename = canonical_filename(
514 file_tracer.source_filename()
515 )
516 break
517 except Exception:
518 self._warn(
519 "Disabling plug-in %r due to an exception:" % (
520 plugin._coverage_plugin_name
521 )
522 )
523 traceback.print_exc()
524 plugin._coverage_enabled = False
525 continue
526 else:
527 # No plugin wanted it: it's Python.
528 disp.trace = True
529 disp.source_filename = canonical
530
531 if not disp.has_dynamic_filename:
532 if not disp.source_filename:
533 raise CoverageException(
534 "Plugin %r didn't set source_filename for %r" %
535 (plugin, disp.original_filename)
536 )
537 reason = self._check_include_omit_etc_internal(
538 disp.source_filename, frame,
539 )
540 if reason:
541 nope(disp, reason)
542
543 return disp
544
545 def _check_include_omit_etc_internal(self, filename, frame):
546 """Check a file name against the include, omit, etc, rules.
547
548 Returns a string or None. String means, don't trace, and is the reason
549 why. None means no reason found to not trace.
550
551 """
552 modulename = self._name_for_module(frame.f_globals, filename)
553
554 # If the user specified source or include, then that's authoritative
555 # about the outer bound of what to measure and we don't have to apply
556 # any canned exclusions. If they didn't, then we have to exclude the
557 # stdlib and coverage.py directories.
558 if self.source_match:
559 if self.source_pkgs_match.match(modulename):
560 if modulename in self.source_pkgs_unmatched:
561 self.source_pkgs_unmatched.remove(modulename)
562 elif not self.source_match.match(filename):
563 return "falls outside the --source trees"
564 elif self.include_match:
565 if not self.include_match.match(filename):
566 return "falls outside the --include trees"
567 else:
568 # If we aren't supposed to trace installed code, then check if this
569 # is near the Python standard library and skip it if so.
570 if self.pylib_match and self.pylib_match.match(filename):
571 return "is in the stdlib"
572
573 # We exclude the coverage.py code itself, since a little of it
574 # will be measured otherwise.
575 if self.cover_match and self.cover_match.match(filename):
576 return "is part of coverage.py"
577
578 # Check the file against the omit pattern.
579 if self.omit_match and self.omit_match.match(filename):
580 return "is inside an --omit pattern"
581
582 # No reason found to skip this file.
583 return None
584
585 def _should_trace(self, filename, frame):
586 """Decide whether to trace execution in `filename`.
587
588 Calls `_should_trace_internal`, and returns the FileDisposition.
589
590 """
591 disp = self._should_trace_internal(filename, frame)
592 if self.debug.should('trace'):
593 self.debug.write(_disposition_debug_msg(disp))
594 return disp
595
596 def _check_include_omit_etc(self, filename, frame):
597 """Check a file name against the include/omit/etc, rules, verbosely.
598
599 Returns a boolean: True if the file should be traced, False if not.
600
601 """
602 reason = self._check_include_omit_etc_internal(filename, frame)
603 if self.debug.should('trace'):
604 if not reason:
605 msg = "Including %r" % (filename,)
606 else:
607 msg = "Not including %r: %s" % (filename, reason)
608 self.debug.write(msg)
609
610 return not reason
611
612 def _warn(self, msg, slug=None):
613 """Use `msg` as a warning.
614
615 For warning suppression, use `slug` as the shorthand.
616 """
617 if slug in self.config.disable_warnings:
618 # Don't issue the warning
619 return
620
621 self._warnings.append(msg)
622 if slug:
623 msg = "%s (%s)" % (msg, slug)
624 if self.debug.should('pid'):
625 msg = "[%d] %s" % (os.getpid(), msg)
626 sys.stderr.write("Coverage.py warning: %s\n" % msg)
627
628 def get_option(self, option_name):
629 """Get an option from the configuration.
630
631 `option_name` is a colon-separated string indicating the section and
632 option name. For example, the ``branch`` option in the ``[run]``
633 section of the config file would be indicated with `"run:branch"`.
634
635 Returns the value of the option.
636
637 .. versionadded:: 4.0
638
639 """
640 return self.config.get_option(option_name)
641
642 def set_option(self, option_name, value):
643 """Set an option in the configuration.
644
645 `option_name` is a colon-separated string indicating the section and
646 option name. For example, the ``branch`` option in the ``[run]``
647 section of the config file would be indicated with ``"run:branch"``.
648
649 `value` is the new value for the option. This should be an
650 appropriate Python value. For example, use True for booleans, not the
651 string ``"True"``.
652
653 As an example, calling::
654
655 cov.set_option("run:branch", True)
656
657 has the same effect as this configuration file::
658
659 [run]
660 branch = True
661
662 .. versionadded:: 4.0
663
664 """
665 self.config.set_option(option_name, value)
666
667 def use_cache(self, usecache):
668 """Obsolete method."""
669 self._init()
670 if not usecache:
671 self._warn("use_cache(False) is no longer supported.")
672
673 def load(self):
674 """Load previously-collected coverage data from the data file."""
675 self._init()
676 self.collector.reset()
677 self.data_files.read(self.data)
678
679 def start(self):
680 """Start measuring code coverage.
681
682 Coverage measurement only occurs in functions called after
683 :meth:`start` is invoked. Statements in the same scope as
684 :meth:`start` won't be measured.
685
686 Once you invoke :meth:`start`, you must also call :meth:`stop`
687 eventually, or your process might not shut down cleanly.
688
689 """
690 self._init()
691 if self.include:
692 if self.source or self.source_pkgs:
693 self._warn("--include is ignored because --source is set", slug="include-ignored")
694 if self.run_suffix:
695 # Calling start() means we're running code, so use the run_suffix
696 # as the data_suffix when we eventually save the data.
697 self.data_suffix = self.run_suffix
698 if self._auto_load:
699 self.load()
700
701 self.collector.start()
702 self._started = True
703
704 def stop(self):
705 """Stop measuring code coverage."""
706 if self._started:
707 self.collector.stop()
708 self._started = False
709
710 def _atexit(self):
711 """Clean up on process shutdown."""
712 if self.debug.should("process"):
713 self.debug.write("atexit: {0!r}".format(self))
714 if self._started:
715 self.stop()
716 if self._auto_save:
717 self.save()
718
719 def erase(self):
720 """Erase previously-collected coverage data.
721
722 This removes the in-memory data collected in this session as well as
723 discarding the data file.
724
725 """
726 self._init()
727 self.collector.reset()
728 self.data.erase()
729 self.data_files.erase(parallel=self.config.parallel)
730
731 def clear_exclude(self, which='exclude'):
732 """Clear the exclude list."""
733 self._init()
734 setattr(self.config, which + "_list", [])
735 self._exclude_regex_stale()
736
737 def exclude(self, regex, which='exclude'):
738 """Exclude source lines from execution consideration.
739
740 A number of lists of regular expressions are maintained. Each list
741 selects lines that are treated differently during reporting.
742
743 `which` determines which list is modified. The "exclude" list selects
744 lines that are not considered executable at all. The "partial" list
745 indicates lines with branches that are not taken.
746
747 `regex` is a regular expression. The regex is added to the specified
748 list. If any of the regexes in the list is found in a line, the line
749 is marked for special treatment during reporting.
750
751 """
752 self._init()
753 excl_list = getattr(self.config, which + "_list")
754 excl_list.append(regex)
755 self._exclude_regex_stale()
756
757 def _exclude_regex_stale(self):
758 """Drop all the compiled exclusion regexes, a list was modified."""
759 self._exclude_re.clear()
760
761 def _exclude_regex(self, which):
762 """Return a compiled regex for the given exclusion list."""
763 if which not in self._exclude_re:
764 excl_list = getattr(self.config, which + "_list")
765 self._exclude_re[which] = join_regex(excl_list)
766 return self._exclude_re[which]
767
768 def get_exclude_list(self, which='exclude'):
769 """Return a list of excluded regex patterns.
770
771 `which` indicates which list is desired. See :meth:`exclude` for the
772 lists that are available, and their meaning.
773
774 """
775 self._init()
776 return getattr(self.config, which + "_list")
777
778 def save(self):
779 """Save the collected coverage data to the data file."""
780 self._init()
781 self.get_data()
782 self.data_files.write(self.data, suffix=self.data_suffix)
783
784 def combine(self, data_paths=None, strict=False):
785 """Combine together a number of similarly-named coverage data files.
786
787 All coverage data files whose name starts with `data_file` (from the
788 coverage() constructor) will be read, and combined together into the
789 current measurements.
790
791 `data_paths` is a list of files or directories from which data should
792 be combined. If no list is passed, then the data files from the
793 directory indicated by the current data file (probably the current
794 directory) will be combined.
795
796 If `strict` is true, then it is an error to attempt to combine when
797 there are no data files to combine.
798
799 .. versionadded:: 4.0
800 The `data_paths` parameter.
801
802 .. versionadded:: 4.3
803 The `strict` parameter.
804
805 """
806 self._init()
807 self.get_data()
808
809 aliases = None
810 if self.config.paths:
811 aliases = PathAliases()
812 for paths in self.config.paths.values():
813 result = paths[0]
814 for pattern in paths[1:]:
815 aliases.add(pattern, result)
816
817 self.data_files.combine_parallel_data(
818 self.data, aliases=aliases, data_paths=data_paths, strict=strict,
819 )
820
821 def get_data(self):
822 """Get the collected data.
823
824 Also warn about various problems collecting data.
825
826 Returns a :class:`coverage.CoverageData`, the collected coverage data.
827
828 .. versionadded:: 4.0
829
830 """
831 self._init()
832
833 if self.collector.save_data(self.data):
834 self._post_save_work()
835
836 return self.data
837
838 def _post_save_work(self):
839 """After saving data, look for warnings, post-work, etc.
840
841 Warn about things that should have happened but didn't.
842 Look for unexecuted files.
843
844 """
845 # If there are still entries in the source_pkgs_unmatched list,
846 # then we never encountered those packages.
847 if self._warn_unimported_source:
848 for pkg in self.source_pkgs_unmatched:
849 self._warn_about_unmeasured_code(pkg)
850
851 # Find out if we got any data.
852 if not self.data and self._warn_no_data:
853 self._warn("No data was collected.", slug="no-data-collected")
854
855 # Find files that were never executed at all.
856 for pkg in self.source_pkgs:
857 if (not pkg in sys.modules or
858 not module_has_file(sys.modules[pkg])):
859 continue
860 pkg_file = source_for_file(sys.modules[pkg].__file__)
861 self._find_unexecuted_files(self._canonical_path(pkg_file))
862
863 for src in self.source:
864 self._find_unexecuted_files(src)
865
866 if self.config.note:
867 self.data.add_run_info(note=self.config.note)
868
869 def _warn_about_unmeasured_code(self, pkg):
870 """Warn about a package or module that we never traced.
871
872 `pkg` is a string, the name of the package or module.
873
874 """
875 mod = sys.modules.get(pkg)
876 if mod is None:
877 self._warn("Module %s was never imported." % pkg, slug="module-not-imported")
878 return
879
880 if module_is_namespace(mod):
881 # A namespace package. It's OK for this not to have been traced,
882 # since there is no code directly in it.
883 return
884
885 if not module_has_file(mod):
886 self._warn("Module %s has no Python source." % pkg, slug="module-not-python")
887 return
888
889 # The module was in sys.modules, and seems like a module with code, but
890 # we never measured it. I guess that means it was imported before
891 # coverage even started.
892 self._warn(
893 "Module %s was previously imported, but not measured" % pkg,
894 slug="module-not-measured",
895 )
896
897 def _find_plugin_files(self, src_dir):
898 """Get executable files from the plugins."""
899 for plugin in self.plugins.file_tracers:
900 for x_file in plugin.find_executable_files(src_dir):
901 yield x_file, plugin._coverage_plugin_name
902
903 def _find_unexecuted_files(self, src_dir):
904 """Find unexecuted files in `src_dir`.
905
906 Search for files in `src_dir` that are probably importable,
907 and add them as unexecuted files in `self.data`.
908
909 """
910 py_files = ((py_file, None) for py_file in find_python_files(src_dir))
911 plugin_files = self._find_plugin_files(src_dir)
912
913 for file_path, plugin_name in itertools.chain(py_files, plugin_files):
914 file_path = canonical_filename(file_path)
915 if self.omit_match and self.omit_match.match(file_path):
916 # Turns out this file was omitted, so don't pull it back
917 # in as unexecuted.
918 continue
919 self.data.touch_file(file_path, plugin_name)
920
921 # Backward compatibility with version 1.
922 def analysis(self, morf):
923 """Like `analysis2` but doesn't return excluded line numbers."""
924 f, s, _, m, mf = self.analysis2(morf)
925 return f, s, m, mf
926
927 def analysis2(self, morf):
928 """Analyze a module.
929
930 `morf` is a module or a file name. It will be analyzed to determine
931 its coverage statistics. The return value is a 5-tuple:
932
933 * The file name for the module.
934 * A list of line numbers of executable statements.
935 * A list of line numbers of excluded statements.
936 * A list of line numbers of statements not run (missing from
937 execution).
938 * A readable formatted string of the missing line numbers.
939
940 The analysis uses the source file itself and the current measured
941 coverage data.
942
943 """
944 self._init()
945 analysis = self._analyze(morf)
946 return (
947 analysis.filename,
948 sorted(analysis.statements),
949 sorted(analysis.excluded),
950 sorted(analysis.missing),
951 analysis.missing_formatted(),
952 )
953
954 def _analyze(self, it):
955 """Analyze a single morf or code unit.
956
957 Returns an `Analysis` object.
958
959 """
960 self.get_data()
961 if not isinstance(it, FileReporter):
962 it = self._get_file_reporter(it)
963
964 return Analysis(self.data, it)
965
966 def _get_file_reporter(self, morf):
967 """Get a FileReporter for a module or file name."""
968 plugin = None
969 file_reporter = "python"
970
971 if isinstance(morf, string_class):
972 abs_morf = abs_file(morf)
973 plugin_name = self.data.file_tracer(abs_morf)
974 if plugin_name:
975 plugin = self.plugins.get(plugin_name)
976
977 if plugin:
978 file_reporter = plugin.file_reporter(abs_morf)
979 if file_reporter is None:
980 raise CoverageException(
981 "Plugin %r did not provide a file reporter for %r." % (
982 plugin._coverage_plugin_name, morf
983 )
984 )
985
986 if file_reporter == "python":
987 file_reporter = PythonFileReporter(morf, self)
988
989 return file_reporter
990
991 def _get_file_reporters(self, morfs=None):
992 """Get a list of FileReporters for a list of modules or file names.
993
994 For each module or file name in `morfs`, find a FileReporter. Return
995 the list of FileReporters.
996
997 If `morfs` is a single module or file name, this returns a list of one
998 FileReporter. If `morfs` is empty or None, then the list of all files
999 measured is used to find the FileReporters.
1000
1001 """
1002 if not morfs:
1003 morfs = self.data.measured_files()
1004
1005 # Be sure we have a list.
1006 if not isinstance(morfs, (list, tuple)):
1007 morfs = [morfs]
1008
1009 file_reporters = []
1010 for morf in morfs:
1011 file_reporter = self._get_file_reporter(morf)
1012 file_reporters.append(file_reporter)
1013
1014 return file_reporters
1015
1016 def report(
1017 self, morfs=None, show_missing=None, ignore_errors=None,
1018 file=None, # pylint: disable=redefined-builtin
1019 omit=None, include=None, skip_covered=None,
1020 ):
1021 """Write a summary report to `file`.
1022
1023 Each module in `morfs` is listed, with counts of statements, executed
1024 statements, missing statements, and a list of lines missed.
1025
1026 `include` is a list of file name patterns. Files that match will be
1027 included in the report. Files matching `omit` will not be included in
1028 the report.
1029
1030 If `skip_covered` is True, don't report on files with 100% coverage.
1031
1032 Returns a float, the total percentage covered.
1033
1034 """
1035 self.get_data()
1036 self.config.from_args(
1037 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1038 show_missing=show_missing, skip_covered=skip_covered,
1039 )
1040 reporter = SummaryReporter(self, self.config)
1041 return reporter.report(morfs, outfile=file)
1042
1043 def annotate(
1044 self, morfs=None, directory=None, ignore_errors=None,
1045 omit=None, include=None,
1046 ):
1047 """Annotate a list of modules.
1048
1049 Each module in `morfs` is annotated. The source is written to a new
1050 file, named with a ",cover" suffix, with each line prefixed with a
1051 marker to indicate the coverage of the line. Covered lines have ">",
1052 excluded lines have "-", and missing lines have "!".
1053
1054 See :meth:`report` for other arguments.
1055
1056 """
1057 self.get_data()
1058 self.config.from_args(
1059 ignore_errors=ignore_errors, report_omit=omit, report_include=include
1060 )
1061 reporter = AnnotateReporter(self, self.config)
1062 reporter.report(morfs, directory=directory)
1063
1064 def html_report(self, morfs=None, directory=None, ignore_errors=None,
1065 omit=None, include=None, extra_css=None, title=None,
1066 skip_covered=None):
1067 """Generate an HTML report.
1068
1069 The HTML is written to `directory`. The file "index.html" is the
1070 overview starting point, with links to more detailed pages for
1071 individual modules.
1072
1073 `extra_css` is a path to a file of other CSS to apply on the page.
1074 It will be copied into the HTML directory.
1075
1076 `title` is a text string (not HTML) to use as the title of the HTML
1077 report.
1078
1079 See :meth:`report` for other arguments.
1080
1081 Returns a float, the total percentage covered.
1082
1083 """
1084 self.get_data()
1085 self.config.from_args(
1086 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1087 html_dir=directory, extra_css=extra_css, html_title=title,
1088 skip_covered=skip_covered,
1089 )
1090 reporter = HtmlReporter(self, self.config)
1091 return reporter.report(morfs)
1092
1093 def xml_report(
1094 self, morfs=None, outfile=None, ignore_errors=None,
1095 omit=None, include=None,
1096 ):
1097 """Generate an XML report of coverage results.
1098
1099 The report is compatible with Cobertura reports.
1100
1101 Each module in `morfs` is included in the report. `outfile` is the
1102 path to write the file to, "-" will write to stdout.
1103
1104 See :meth:`report` for other arguments.
1105
1106 Returns a float, the total percentage covered.
1107
1108 """
1109 self.get_data()
1110 self.config.from_args(
1111 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1112 xml_output=outfile,
1113 )
1114 file_to_close = None
1115 delete_file = False
1116 if self.config.xml_output:
1117 if self.config.xml_output == '-':
1118 outfile = sys.stdout
1119 else:
1120 # Ensure that the output directory is created; done here
1121 # because this report pre-opens the output file.
1122 # HTMLReport does this using the Report plumbing because
1123 # its task is more complex, being multiple files.
1124 output_dir = os.path.dirname(self.config.xml_output)
1125 if output_dir and not os.path.isdir(output_dir):
1126 os.makedirs(output_dir)
1127 open_kwargs = {}
1128 if env.PY3:
1129 open_kwargs['encoding'] = 'utf8'
1130 outfile = open(self.config.xml_output, "w", **open_kwargs)
1131 file_to_close = outfile
1132 try:
1133 reporter = XmlReporter(self, self.config)
1134 return reporter.report(morfs, outfile=outfile)
1135 except CoverageException:
1136 delete_file = True
1137 raise
1138 finally:
1139 if file_to_close:
1140 file_to_close.close()
1141 if delete_file:
1142 file_be_gone(self.config.xml_output)
1143
1144 def sys_info(self):
1145 """Return a list of (key, value) pairs showing internal information."""
1146
1147 import coverage as covmod
1148
1149 self._init()
1150
1151 def plugin_info(plugins):
1152 """Make an entry for the sys_info from a list of plug-ins."""
1153 entries = []
1154 for plugin in plugins:
1155 entry = plugin._coverage_plugin_name
1156 if not plugin._coverage_enabled:
1157 entry += " (disabled)"
1158 entries.append(entry)
1159 return entries
1160
1161 info = [
1162 ('version', covmod.__version__),
1163 ('coverage', covmod.__file__),
1164 ('cover_paths', self.cover_paths),
1165 ('pylib_paths', self.pylib_paths),
1166 ('tracer', self.collector.tracer_name()),
1167 ('plugins.file_tracers', plugin_info(self.plugins.file_tracers)),
1168 ('plugins.configurers', plugin_info(self.plugins.configurers)),
1169 ('config_files', self.config.attempted_config_files),
1170 ('configs_read', self.config.config_files),
1171 ('data_path', self.data_files.filename),
1172 ('python', sys.version.replace('\n', '')),
1173 ('platform', platform.platform()),
1174 ('implementation', platform.python_implementation()),
1175 ('executable', sys.executable),
1176 ('cwd', os.getcwd()),
1177 ('path', sys.path),
1178 ('environment', sorted(
1179 ("%s = %s" % (k, v))
1180 for k, v in iitems(os.environ)
1181 if k.startswith(("COV", "PY"))
1182 )),
1183 ('command_line', " ".join(getattr(sys, 'argv', ['???']))),
1184 ]
1185
1186 matcher_names = [
1187 'source_match', 'source_pkgs_match',
1188 'include_match', 'omit_match',
1189 'cover_match', 'pylib_match',
1190 ]
1191
1192 for matcher_name in matcher_names:
1193 matcher = getattr(self, matcher_name)
1194 if matcher:
1195 matcher_info = matcher.info()
1196 else:
1197 matcher_info = '-none-'
1198 info.append((matcher_name, matcher_info))
1199
1200 return info
1201
1202
1203 def module_is_namespace(mod):
1204 """Is the module object `mod` a PEP420 namespace module?"""
1205 return hasattr(mod, '__path__') and getattr(mod, '__file__', None) is None
1206
1207
1208 def module_has_file(mod):
1209 """Does the module object `mod` have an existing __file__ ?"""
1210 mod__file__ = getattr(mod, '__file__', None)
1211 if mod__file__ is None:
1212 return False
1213 return os.path.exists(mod__file__)
1214
1215
1216 # FileDisposition "methods": FileDisposition is a pure value object, so it can
1217 # be implemented in either C or Python. Acting on them is done with these
1218 # functions.
1219
1220 def _disposition_init(cls, original_filename):
1221 """Construct and initialize a new FileDisposition object."""
1222 disp = cls()
1223 disp.original_filename = original_filename
1224 disp.canonical_filename = original_filename
1225 disp.source_filename = None
1226 disp.trace = False
1227 disp.reason = ""
1228 disp.file_tracer = None
1229 disp.has_dynamic_filename = False
1230 return disp
1231
1232
1233 def _disposition_debug_msg(disp):
1234 """Make a nice debug message of what the FileDisposition is doing."""
1235 if disp.trace:
1236 msg = "Tracing %r" % (disp.original_filename,)
1237 if disp.file_tracer:
1238 msg += ": will be traced by %r" % disp.file_tracer
1239 else:
1240 msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason)
1241 return msg
1242
1243
1244 def process_startup():
1245 """Call this at Python start-up to perhaps measure coverage.
1246
1247 If the environment variable COVERAGE_PROCESS_START is defined, coverage
1248 measurement is started. The value of the variable is the config file
1249 to use.
1250
1251 There are two ways to configure your Python installation to invoke this
1252 function when Python starts:
1253
1254 #. Create or append to sitecustomize.py to add these lines::
1255
1256 import coverage
1257 coverage.process_startup()
1258
1259 #. Create a .pth file in your Python installation containing::
1260
1261 import coverage; coverage.process_startup()
1262
1263 Returns the :class:`Coverage` instance that was started, or None if it was
1264 not started by this call.
1265
1266 """
1267 cps = os.environ.get("COVERAGE_PROCESS_START")
1268 if not cps:
1269 # No request for coverage, nothing to do.
1270 return None
1271
1272 # This function can be called more than once in a process. This happens
1273 # because some virtualenv configurations make the same directory visible
1274 # twice in sys.path. This means that the .pth file will be found twice,
1275 # and executed twice, executing this function twice. We set a global
1276 # flag (an attribute on this function) to indicate that coverage.py has
1277 # already been started, so we can avoid doing it twice.
1278 #
1279 # https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy has more
1280 # details.
1281
1282 if hasattr(process_startup, "coverage"):
1283 # We've annotated this function before, so we must have already
1284 # started coverage.py in this process. Nothing to do.
1285 return None
1286
1287 cov = Coverage(config_file=cps)
1288 process_startup.coverage = cov
1289 cov.start()
1290 cov._warn_no_data = False
1291 cov._warn_unimported_source = False
1292 cov._auto_save = True
1293
1294 return cov
1295
1296
1297 def _prevent_sub_process_measurement():
1298 """Stop any subprocess auto-measurement from writing data."""
1299 auto_created_coverage = getattr(process_startup, "coverage", None)
1300 if auto_created_coverage is not None:
1301 auto_created_coverage._auto_save = False

eric ide

mercurial