DebugClients/Python/coverage/control.py

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

eric ide

mercurial