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