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 |
|