DebugClients/Python/coverage/control.py

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

eric ide

mercurial