DebugClients/Python/coverage/control.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
child 6649
f1b3a73831c9
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 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 2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3 3
4 """Core control stuff for coverage.py.""" 4 """Core control stuff for coverage.py."""
5 5
6
6 import atexit 7 import atexit
7 import inspect 8 import inspect
9 import itertools
8 import os 10 import os
9 import platform 11 import platform
10 import re 12 import re
11 import sys 13 import sys
14 import time
12 import traceback 15 import traceback
13 16
14 from coverage import env, files 17 from coverage import env
15 from coverage.annotate import AnnotateReporter 18 from coverage.annotate import AnnotateReporter
16 from coverage.backward import string_class, iitems 19 from coverage.backward import string_class, iitems
17 from coverage.collector import Collector 20 from coverage.collector import Collector
18 from coverage.config import CoverageConfig 21 from coverage.config import read_coverage_config
19 from coverage.data import CoverageData, CoverageDataFiles 22 from coverage.data import CoverageData, CoverageDataFiles
20 from coverage.debug import DebugControl 23 from coverage.debug import DebugControl, write_formatted_info
21 from coverage.files import TreeMatcher, FnmatchMatcher 24 from coverage.files import TreeMatcher, FnmatchMatcher
22 from coverage.files import PathAliases, find_python_files, prep_patterns 25 from coverage.files import PathAliases, find_python_files, prep_patterns
26 from coverage.files import canonical_filename, set_relative_directory
23 from coverage.files import ModuleMatcher, abs_file 27 from coverage.files import ModuleMatcher, abs_file
24 from coverage.html import HtmlReporter 28 from coverage.html import HtmlReporter
25 from coverage.misc import CoverageException, bool_or_none, join_regex 29 from coverage.misc import CoverageException, bool_or_none, join_regex
26 from coverage.misc import file_be_gone, isolate_module 30 from coverage.misc import file_be_gone, isolate_module
27 from coverage.monkey import patch_multiprocessing
28 from coverage.plugin import FileReporter 31 from coverage.plugin import FileReporter
29 from coverage.plugin_support import Plugins 32 from coverage.plugin_support import Plugins
30 from coverage.python import PythonFileReporter 33 from coverage.python import PythonFileReporter, source_for_file
31 from coverage.results import Analysis, Numbers 34 from coverage.results import Analysis, Numbers
32 from coverage.summary import SummaryReporter 35 from coverage.summary import SummaryReporter
33 from coverage.xmlreport import XmlReporter 36 from coverage.xmlreport import XmlReporter
34 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
35 os = isolate_module(os) 44 os = isolate_module(os)
36 45
37 # Pypy has some unusual stuff in the "stdlib". Consider those locations 46 # Pypy has some unusual stuff in the "stdlib". Consider those locations
38 # when deciding where the stdlib is. 47 # when deciding where the stdlib is. These modules are not used for anything,
39 try: 48 # they are modules importable from the pypy lib directories, so that we can
40 import _structseq 49 # find those directories.
41 except ImportError: 50 _structseq = _pypy_irc_topic = None
42 _structseq = 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
43 61
44 62
45 class Coverage(object): 63 class Coverage(object):
46 """Programmatic access to coverage.py. 64 """Programmatic access to coverage.py.
47 65
90 108
91 * If it is a string, it is the name of the file to read. If the 109 * If it is a string, it is the name of the file to read. If the
92 file can't be read, it is an error. 110 file can't be read, it is an error.
93 111
94 * If it is True, then a few standard files names are tried 112 * If it is True, then a few standard files names are tried
95 (".coveragerc", "setup.cfg"). It is not an error for these files 113 (".coveragerc", "setup.cfg", "tox.ini"). It is not an error for
96 to not be found. 114 these files to not be found.
97 115
98 * If it is False, then no configuration file is read. 116 * If it is False, then no configuration file is read.
99 117
100 `source` is a list of file paths or package names. Only code located 118 `source` is a list of file paths or package names. Only code located
101 in the trees indicated by the file paths or package names will be 119 in the trees indicated by the file paths or package names will be
108 `debug` is a list of strings indicating what debugging information is 126 `debug` is a list of strings indicating what debugging information is
109 desired. 127 desired.
110 128
111 `concurrency` is a string indicating the concurrency library being used 129 `concurrency` is a string indicating the concurrency library being used
112 in the measured code. Without this, coverage.py will get incorrect 130 in the measured code. Without this, coverage.py will get incorrect
113 results. Valid strings are "greenlet", "eventlet", "gevent", 131 results if these libraries are in use. Valid strings are "greenlet",
114 "multiprocessing", or "thread" (the default). 132 "eventlet", "gevent", "multiprocessing", or "thread" (the default).
133 This can also be a list of these strings.
115 134
116 .. versionadded:: 4.0 135 .. versionadded:: 4.0
117 The `concurrency` parameter. 136 The `concurrency` parameter.
118 137
119 """ 138 .. versionadded:: 4.2
120 # Build our configuration from a number of sources: 139 The `concurrency` parameter can now be a list of strings.
121 # 1: defaults: 140
122 self.config = CoverageConfig() 141 """
123 142 # Build our configuration from a number of sources.
124 # 2: from the rcfile, .coveragerc or setup.cfg file: 143 self.config_file, self.config = read_coverage_config(
125 if config_file: 144 config_file=config_file,
126 did_read_rc = False
127 # Some API users were specifying ".coveragerc" to mean the same as
128 # True, so make it so.
129 if config_file == ".coveragerc":
130 config_file = True
131 specified_file = (config_file is not True)
132 if not specified_file:
133 config_file = ".coveragerc"
134
135 did_read_rc = self.config.from_file(config_file)
136
137 if not did_read_rc:
138 if specified_file:
139 raise CoverageException(
140 "Couldn't read '%s' as a config file" % config_file
141 )
142 self.config.from_file("setup.cfg", section_prefix="coverage:")
143
144 # 3: from environment variables:
145 env_data_file = os.environ.get('COVERAGE_FILE')
146 if env_data_file:
147 self.config.data_file = env_data_file
148 debugs = os.environ.get('COVERAGE_DEBUG')
149 if debugs:
150 self.config.debug.extend(debugs.split(","))
151
152 # 4: from constructor arguments:
153 self.config.from_args(
154 data_file=data_file, cover_pylib=cover_pylib, timid=timid, 145 data_file=data_file, cover_pylib=cover_pylib, timid=timid,
155 branch=branch, parallel=bool_or_none(data_suffix), 146 branch=branch, parallel=bool_or_none(data_suffix),
156 source=source, omit=omit, include=include, debug=debug, 147 source=source, run_omit=omit, run_include=include, debug=debug,
148 report_omit=omit, report_include=include,
157 concurrency=concurrency, 149 concurrency=concurrency,
158 ) 150 )
159 151
152 # This is injectable by tests.
160 self._debug_file = None 153 self._debug_file = None
161 self._auto_data = auto_data 154
155 self._auto_load = self._auto_save = auto_data
162 self._data_suffix = data_suffix 156 self._data_suffix = data_suffix
163 157
164 # The matchers for _should_trace. 158 # The matchers for _should_trace.
165 self.source_match = None 159 self.source_match = None
166 self.source_pkgs_match = None 160 self.source_pkgs_match = None
174 # A record of all the warnings that have been issued. 168 # A record of all the warnings that have been issued.
175 self._warnings = [] 169 self._warnings = []
176 170
177 # Other instance attributes, set later. 171 # Other instance attributes, set later.
178 self.omit = self.include = self.source = None 172 self.omit = self.include = self.source = None
173 self.source_pkgs_unmatched = None
179 self.source_pkgs = None 174 self.source_pkgs = None
180 self.data = self.data_files = self.collector = None 175 self.data = self.data_files = self.collector = None
181 self.plugins = None 176 self.plugins = None
182 self.pylib_dirs = self.cover_dirs = None 177 self.pylib_paths = self.cover_paths = None
183 self.data_suffix = self.run_suffix = None 178 self.data_suffix = self.run_suffix = None
184 self._exclude_re = None 179 self._exclude_re = None
185 self.debug = None 180 self.debug = None
186 181
187 # State machine variables: 182 # State machine variables:
188 # Have we initialized everything? 183 # Have we initialized everything?
189 self._inited = False 184 self._inited = False
190 # Have we started collecting and not stopped it? 185 # Have we started collecting and not stopped it?
191 self._started = False 186 self._started = False
192 # Have we measured some data and not harvested it? 187
193 self._measured = False 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()
194 195
195 def _init(self): 196 def _init(self):
196 """Set all the initial state. 197 """Set all the initial state.
197 198
198 This is called by the public methods to initialize state. This lets us 199 This is called by the public methods to initialize state. This lets us
200 function is called. 201 function is called.
201 202
202 """ 203 """
203 if self._inited: 204 if self._inited:
204 return 205 return
206
207 self._inited = True
205 208
206 # Create and configure the debugging controller. COVERAGE_DEBUG_FILE 209 # Create and configure the debugging controller. COVERAGE_DEBUG_FILE
207 # is an environment variable, the name of a file to append debug logs 210 # is an environment variable, the name of a file to append debug logs
208 # to. 211 # to.
209 if self._debug_file is None: 212 if self._debug_file is None:
212 self._debug_file = open(debug_file_name, "a") 215 self._debug_file = open(debug_file_name, "a")
213 else: 216 else:
214 self._debug_file = sys.stderr 217 self._debug_file = sys.stderr
215 self.debug = DebugControl(self.config.debug, self._debug_file) 218 self.debug = DebugControl(self.config.debug, self._debug_file)
216 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
217 # Load plugins 225 # Load plugins
218 self.plugins = Plugins.load_plugins(self.config.plugins, self.config, self.debug) 226 self.plugins = Plugins.load_plugins(self.config.plugins, self.config, self.debug)
219 227
220 # _exclude_re is a dict that maps exclusion list names to compiled 228 # Run configuring plugins.
221 # regexes. 229 for plugin in self.plugins.configurers:
222 self._exclude_re = {} 230 # We need an object with set_option and get_option. Either self or
223 self._exclude_regex_stale() 231 # self.config will do. Choosing randomly stops people from doing
224 232 # other things with those objects, against the public API. Yes,
225 files.set_relative_directory() 233 # this is a bit childish. :)
234 plugin.configure([self, self.config][int(time.time()) % 2])
226 235
227 # The source argument can be directories or package names. 236 # The source argument can be directories or package names.
228 self.source = [] 237 self.source = []
229 self.source_pkgs = [] 238 self.source_pkgs = []
230 for src in self.config.source or []: 239 for src in self.config.source or []:
231 if os.path.exists(src): 240 if os.path.isdir(src):
232 self.source.append(files.canonical_filename(src)) 241 self.source.append(canonical_filename(src))
233 else: 242 else:
234 self.source_pkgs.append(src) 243 self.source_pkgs.append(src)
235 244 self.source_pkgs_unmatched = self.source_pkgs[:]
236 self.omit = prep_patterns(self.config.omit) 245
237 self.include = prep_patterns(self.config.include) 246 self.omit = prep_patterns(self.config.run_omit)
238 247 self.include = prep_patterns(self.config.run_include)
239 concurrency = self.config.concurrency 248
240 if concurrency == "multiprocessing": 249 concurrency = self.config.concurrency or []
241 patch_multiprocessing() 250 if "multiprocessing" in concurrency:
242 concurrency = None 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
243 259
244 self.collector = Collector( 260 self.collector = Collector(
245 should_trace=self._should_trace, 261 should_trace=self._should_trace,
246 check_include=self._check_include_omit_etc, 262 check_include=self._check_include_omit_etc,
247 timid=self.config.timid, 263 timid=self.config.timid,
279 295
280 # Create the data file. We do this at construction time so that the 296 # Create the data file. We do this at construction time so that the
281 # data file will be written into the directory where the process 297 # data file will be written into the directory where the process
282 # started rather than wherever the process eventually chdir'd to. 298 # started rather than wherever the process eventually chdir'd to.
283 self.data = CoverageData(debug=self.debug) 299 self.data = CoverageData(debug=self.debug)
284 self.data_files = CoverageDataFiles(basename=self.config.data_file, warn=self._warn) 300 self.data_files = CoverageDataFiles(
301 basename=self.config.data_file, warn=self._warn, debug=self.debug,
302 )
285 303
286 # The directories for files considered "installed with the interpreter". 304 # The directories for files considered "installed with the interpreter".
287 self.pylib_dirs = set() 305 self.pylib_paths = set()
288 if not self.config.cover_pylib: 306 if not self.config.cover_pylib:
289 # Look at where some standard modules are located. That's the 307 # Look at where some standard modules are located. That's the
290 # indication for "installed with the interpreter". In some 308 # indication for "installed with the interpreter". In some
291 # environments (virtualenv, for example), these modules may be 309 # environments (virtualenv, for example), these modules may be
292 # spread across a few locations. Look at all the candidate modules 310 # spread across a few locations. Look at all the candidate modules
293 # we've imported, and take all the different ones. 311 # we've imported, and take all the different ones.
294 for m in (atexit, inspect, os, platform, re, _structseq, traceback): 312 for m in (atexit, inspect, os, platform, _pypy_irc_topic, re, _structseq, traceback):
295 if m is not None and hasattr(m, "__file__"): 313 if m is not None and hasattr(m, "__file__"):
296 self.pylib_dirs.add(self._canonical_dir(m)) 314 self.pylib_paths.add(self._canonical_path(m, directory=True))
315
297 if _structseq and not hasattr(_structseq, '__file__'): 316 if _structseq and not hasattr(_structseq, '__file__'):
298 # PyPy 2.4 has no __file__ in the builtin modules, but the code 317 # PyPy 2.4 has no __file__ in the builtin modules, but the code
299 # objects still have the file names. So dig into one to find 318 # objects still have the file names. So dig into one to find
300 # the path to exclude. 319 # the path to exclude.
301 structseq_new = _structseq.structseq_new 320 structseq_new = _structseq.structseq_new
302 try: 321 try:
303 structseq_file = structseq_new.func_code.co_filename 322 structseq_file = structseq_new.func_code.co_filename
304 except AttributeError: 323 except AttributeError:
305 structseq_file = structseq_new.__code__.co_filename 324 structseq_file = structseq_new.__code__.co_filename
306 self.pylib_dirs.add(self._canonical_dir(structseq_file)) 325 self.pylib_paths.add(self._canonical_path(structseq_file))
307 326
308 # To avoid tracing the coverage.py code itself, we skip anything 327 # To avoid tracing the coverage.py code itself, we skip anything
309 # located where we are. 328 # located where we are.
310 self.cover_dirs = [self._canonical_dir(__file__)] 329 self.cover_paths = [self._canonical_path(__file__, directory=True)]
311 if env.TESTING: 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
312 # When testing, we use PyContracts, which should be considered 334 # When testing, we use PyContracts, which should be considered
313 # part of coverage.py, and it uses six. Exclude those directories 335 # part of coverage.py, and it uses six. Exclude those directories
314 # just as we exclude ourselves. 336 # just as we exclude ourselves.
315 import contracts, six 337 import contracts
338 import six
316 for mod in [contracts, six]: 339 for mod in [contracts, six]:
317 self.cover_dirs.append(self._canonical_dir(mod)) 340 self.cover_paths.append(self._canonical_path(mod))
318 341
319 # Set the reporting precision. 342 # Set the reporting precision.
320 Numbers.set_precision(self.config.precision) 343 Numbers.set_precision(self.config.precision)
321 344
322 atexit.register(self._atexit) 345 atexit.register(self._atexit)
323
324 self._inited = True
325 346
326 # Create the matchers we need for _should_trace 347 # Create the matchers we need for _should_trace
327 if self.source or self.source_pkgs: 348 if self.source or self.source_pkgs:
328 self.source_match = TreeMatcher(self.source) 349 self.source_match = TreeMatcher(self.source)
329 self.source_pkgs_match = ModuleMatcher(self.source_pkgs) 350 self.source_pkgs_match = ModuleMatcher(self.source_pkgs)
330 else: 351 else:
331 if self.cover_dirs: 352 if self.cover_paths:
332 self.cover_match = TreeMatcher(self.cover_dirs) 353 self.cover_match = TreeMatcher(self.cover_paths)
333 if self.pylib_dirs: 354 if self.pylib_paths:
334 self.pylib_match = TreeMatcher(self.pylib_dirs) 355 self.pylib_match = TreeMatcher(self.pylib_paths)
335 if self.include: 356 if self.include:
336 self.include_match = FnmatchMatcher(self.include) 357 self.include_match = FnmatchMatcher(self.include)
337 if self.omit: 358 if self.omit:
338 self.omit_match = FnmatchMatcher(self.omit) 359 self.omit_match = FnmatchMatcher(self.omit)
339 360
340 # The user may want to debug things, show info if desired. 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."""
341 wrote_any = False 366 wrote_any = False
342 if self.debug.should('config'): 367 with self.debug.without_callers():
343 config_info = sorted(self.config.__dict__.items()) 368 if self.debug.should('config'):
344 self.debug.write_formatted_info("config", config_info) 369 config_info = sorted(self.config.__dict__.items())
345 wrote_any = True 370 write_formatted_info(self.debug, "config", config_info)
346 371 wrote_any = True
347 if self.debug.should('sys'): 372
348 self.debug.write_formatted_info("sys", self.sys_info()) 373 if self.debug.should('sys'):
349 for plugin in self.plugins: 374 write_formatted_info(self.debug, "sys", self.sys_info())
350 header = "sys: " + plugin._coverage_plugin_name 375 for plugin in self.plugins:
351 info = plugin.sys_info() 376 header = "sys: " + plugin._coverage_plugin_name
352 self.debug.write_formatted_info(header, info) 377 info = plugin.sys_info()
353 wrote_any = True 378 write_formatted_info(self.debug, header, info)
379 wrote_any = True
354 380
355 if wrote_any: 381 if wrote_any:
356 self.debug.write_formatted_info("end", ()) 382 write_formatted_info(self.debug, "end", ())
357 383
358 def _canonical_dir(self, morf): 384 def _canonical_path(self, morf, directory=False):
359 """Return the canonical directory of the module or file `morf`.""" 385 """Return the canonical path of the module or file `morf`.
360 morf_filename = PythonFileReporter(morf, self).filename 386
361 return os.path.split(morf_filename)[0] 387 If the module is a package, then return its directory. If it is a
362 388 module, then return its file, unless `directory` is True, in which
363 def _source_for_file(self, filename): 389 case return its enclosing directory.
364 """Return the source file for `filename`. 390
365 391 """
366 Given a file name being traced, return the best guess as to the source 392 morf_path = PythonFileReporter(morf, self).filename
367 file to attribute it to. 393 if morf_path.endswith("__init__.py") or directory:
368 394 morf_path = os.path.split(morf_path)[0]
369 """ 395 return morf_path
370 if filename.endswith(".py"):
371 # .py files are themselves source files.
372 return filename
373
374 elif filename.endswith((".pyc", ".pyo")):
375 # Bytecode files probably have source files near them.
376 py_filename = filename[:-1]
377 if os.path.exists(py_filename):
378 # Found a .py file, use that.
379 return py_filename
380 if env.WINDOWS:
381 # On Windows, it could be a .pyw file.
382 pyw_filename = py_filename + "w"
383 if os.path.exists(pyw_filename):
384 return pyw_filename
385 # Didn't find source, but it's probably the .py file we want.
386 return py_filename
387
388 elif filename.endswith("$py.class"):
389 # Jython is easy to guess.
390 return filename[:-9] + ".py"
391
392 # No idea, just use the file name as-is.
393 return filename
394 396
395 def _name_for_module(self, module_globals, filename): 397 def _name_for_module(self, module_globals, filename):
396 """Get the name of the module for a set of globals and file name. 398 """Get the name of the module for a set of globals and file name.
397 399
398 For configurability's sake, we allow __main__ modules to be matched by 400 For configurability's sake, we allow __main__ modules to be matched by
402 full dotted module name, otherwise, we resort to interpreting the 404 full dotted module name, otherwise, we resort to interpreting the
403 file name to get the module's name. In the case that the module name 405 file name to get the module's name. In the case that the module name
404 can't be determined, None is returned. 406 can't be determined, None is returned.
405 407
406 """ 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
407 dunder_name = module_globals.get('__name__', None) 413 dunder_name = module_globals.get('__name__', None)
408 414
409 if isinstance(dunder_name, str) and dunder_name != '__main__': 415 if isinstance(dunder_name, str) and dunder_name != '__main__':
410 # This is the usual case: an imported module. 416 # This is the usual case: an imported module.
411 return dunder_name 417 return dunder_name
450 # the file name at the time the .pyc was compiled. The second name is 456 # the file name at the time the .pyc was compiled. The second name is
451 # __file__, which is where the .pyc was actually loaded from. Since 457 # __file__, which is where the .pyc was actually loaded from. Since
452 # .pyc files can be moved after compilation (for example, by being 458 # .pyc files can be moved after compilation (for example, by being
453 # installed), we look for __file__ in the frame and prefer it to the 459 # installed), we look for __file__ in the frame and prefer it to the
454 # co_filename value. 460 # co_filename value.
455 dunder_file = frame.f_globals.get('__file__') 461 dunder_file = frame.f_globals and frame.f_globals.get('__file__')
456 if dunder_file: 462 if dunder_file:
457 filename = self._source_for_file(dunder_file) 463 filename = source_for_file(dunder_file)
458 if original_filename and not original_filename.startswith('<'): 464 if original_filename and not original_filename.startswith('<'):
459 orig = os.path.basename(original_filename) 465 orig = os.path.basename(original_filename)
460 if orig != os.path.basename(filename): 466 if orig != os.path.basename(filename):
461 # Files shouldn't be renamed when moved. This happens when 467 # Files shouldn't be renamed when moved. This happens when
462 # exec'ing code. If it seems like something is wrong with 468 # exec'ing code. If it seems like something is wrong with
484 490
485 # Jython reports the .class file to the tracer, use the source file. 491 # Jython reports the .class file to the tracer, use the source file.
486 if filename.endswith("$py.class"): 492 if filename.endswith("$py.class"):
487 filename = filename[:-9] + ".py" 493 filename = filename[:-9] + ".py"
488 494
489 canonical = files.canonical_filename(filename) 495 canonical = canonical_filename(filename)
490 disp.canonical_filename = canonical 496 disp.canonical_filename = canonical
491 497
492 # Try the plugins, see if they have an opinion about the file. 498 # Try the plugins, see if they have an opinion about the file.
493 plugin = None 499 plugin = None
494 for plugin in self.plugins.file_tracers: 500 for plugin in self.plugins.file_tracers:
502 disp.trace = True 508 disp.trace = True
503 disp.file_tracer = file_tracer 509 disp.file_tracer = file_tracer
504 if file_tracer.has_dynamic_source_filename(): 510 if file_tracer.has_dynamic_source_filename():
505 disp.has_dynamic_filename = True 511 disp.has_dynamic_filename = True
506 else: 512 else:
507 disp.source_filename = files.canonical_filename( 513 disp.source_filename = canonical_filename(
508 file_tracer.source_filename() 514 file_tracer.source_filename()
509 ) 515 )
510 break 516 break
511 except Exception: 517 except Exception:
512 self._warn( 518 self._warn(
513 "Disabling plugin %r due to an exception:" % ( 519 "Disabling plug-in %r due to an exception:" % (
514 plugin._coverage_plugin_name 520 plugin._coverage_plugin_name
515 ) 521 )
516 ) 522 )
517 traceback.print_exc() 523 traceback.print_exc()
518 plugin._coverage_enabled = False 524 plugin._coverage_enabled = False
549 # about the outer bound of what to measure and we don't have to apply 555 # about the outer bound of what to measure and we don't have to apply
550 # any canned exclusions. If they didn't, then we have to exclude the 556 # any canned exclusions. If they didn't, then we have to exclude the
551 # stdlib and coverage.py directories. 557 # stdlib and coverage.py directories.
552 if self.source_match: 558 if self.source_match:
553 if self.source_pkgs_match.match(modulename): 559 if self.source_pkgs_match.match(modulename):
554 if modulename in self.source_pkgs: 560 if modulename in self.source_pkgs_unmatched:
555 self.source_pkgs.remove(modulename) 561 self.source_pkgs_unmatched.remove(modulename)
556 return None # There's no reason to skip this file. 562 elif not self.source_match.match(filename):
557
558 if not self.source_match.match(filename):
559 return "falls outside the --source trees" 563 return "falls outside the --source trees"
560 elif self.include_match: 564 elif self.include_match:
561 if not self.include_match.match(filename): 565 if not self.include_match.match(filename):
562 return "falls outside the --include trees" 566 return "falls outside the --include trees"
563 else: 567 else:
603 msg = "Not including %r: %s" % (filename, reason) 607 msg = "Not including %r: %s" % (filename, reason)
604 self.debug.write(msg) 608 self.debug.write(msg)
605 609
606 return not reason 610 return not reason
607 611
608 def _warn(self, msg): 612 def _warn(self, msg, slug=None):
609 """Use `msg` as a warning.""" 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
610 self._warnings.append(msg) 621 self._warnings.append(msg)
622 if slug:
623 msg = "%s (%s)" % (msg, slug)
611 if self.debug.should('pid'): 624 if self.debug.should('pid'):
612 msg = "[%d] %s" % (os.getpid(), msg) 625 msg = "[%d] %s" % (os.getpid(), msg)
613 sys.stderr.write("Coverage.py warning: %s\n" % msg) 626 sys.stderr.write("Coverage.py warning: %s\n" % msg)
614 627
615 def get_option(self, option_name): 628 def get_option(self, option_name):
631 644
632 `option_name` is a colon-separated string indicating the section and 645 `option_name` is a colon-separated string indicating the section and
633 option name. For example, the ``branch`` option in the ``[run]`` 646 option name. For example, the ``branch`` option in the ``[run]``
634 section of the config file would be indicated with ``"run:branch"``. 647 section of the config file would be indicated with ``"run:branch"``.
635 648
636 `value` is the new value for the option. This should be a Python 649 `value` is the new value for the option. This should be an
637 value where appropriate. For example, use True for booleans, not the 650 appropriate Python value. For example, use True for booleans, not the
638 string ``"True"``. 651 string ``"True"``.
639 652
640 As an example, calling:: 653 As an example, calling::
641 654
642 cov.set_option("run:branch", True) 655 cov.set_option("run:branch", True)
664 self.data_files.read(self.data) 677 self.data_files.read(self.data)
665 678
666 def start(self): 679 def start(self):
667 """Start measuring code coverage. 680 """Start measuring code coverage.
668 681
669 Coverage measurement actually occurs in functions called after 682 Coverage measurement only occurs in functions called after
670 :meth:`start` is invoked. Statements in the same scope as 683 :meth:`start` is invoked. Statements in the same scope as
671 :meth:`start` won't be measured. 684 :meth:`start` won't be measured.
672 685
673 Once you invoke :meth:`start`, you must also call :meth:`stop` 686 Once you invoke :meth:`start`, you must also call :meth:`stop`
674 eventually, or your process might not shut down cleanly. 687 eventually, or your process might not shut down cleanly.
675 688
676 """ 689 """
677 self._init() 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")
678 if self.run_suffix: 694 if self.run_suffix:
679 # Calling start() means we're running code, so use the run_suffix 695 # Calling start() means we're running code, so use the run_suffix
680 # as the data_suffix when we eventually save the data. 696 # as the data_suffix when we eventually save the data.
681 self.data_suffix = self.run_suffix 697 self.data_suffix = self.run_suffix
682 if self._auto_data: 698 if self._auto_load:
683 self.load() 699 self.load()
684 700
685 self.collector.start() 701 self.collector.start()
686 self._started = True 702 self._started = True
687 self._measured = True
688 703
689 def stop(self): 704 def stop(self):
690 """Stop measuring code coverage.""" 705 """Stop measuring code coverage."""
691 if self._started: 706 if self._started:
692 self.collector.stop() 707 self.collector.stop()
693 self._started = False 708 self._started = False
694 709
695 def _atexit(self): 710 def _atexit(self):
696 """Clean up on process shutdown.""" 711 """Clean up on process shutdown."""
712 if self.debug.should("process"):
713 self.debug.write("atexit: {0!r}".format(self))
697 if self._started: 714 if self._started:
698 self.stop() 715 self.stop()
699 if self._auto_data: 716 if self._auto_save:
700 self.save() 717 self.save()
701 718
702 def erase(self): 719 def erase(self):
703 """Erase previously-collected coverage data. 720 """Erase previously-collected coverage data.
704 721
762 """Save the collected coverage data to the data file.""" 779 """Save the collected coverage data to the data file."""
763 self._init() 780 self._init()
764 self.get_data() 781 self.get_data()
765 self.data_files.write(self.data, suffix=self.data_suffix) 782 self.data_files.write(self.data, suffix=self.data_suffix)
766 783
767 def combine(self, data_paths=None): 784 def combine(self, data_paths=None, strict=False):
768 """Combine together a number of similarly-named coverage data files. 785 """Combine together a number of similarly-named coverage data files.
769 786
770 All coverage data files whose name starts with `data_file` (from the 787 All coverage data files whose name starts with `data_file` (from the
771 coverage() constructor) will be read, and combined together into the 788 coverage() constructor) will be read, and combined together into the
772 current measurements. 789 current measurements.
774 `data_paths` is a list of files or directories from which data should 791 `data_paths` is a list of files or directories from which data should
775 be combined. If no list is passed, then the data files from the 792 be combined. If no list is passed, then the data files from the
776 directory indicated by the current data file (probably the current 793 directory indicated by the current data file (probably the current
777 directory) will be combined. 794 directory) will be combined.
778 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
779 .. versionadded:: 4.0 799 .. versionadded:: 4.0
780 The `data_paths` parameter. 800 The `data_paths` parameter.
801
802 .. versionadded:: 4.3
803 The `strict` parameter.
781 804
782 """ 805 """
783 self._init() 806 self._init()
784 self.get_data() 807 self.get_data()
785 808
789 for paths in self.config.paths.values(): 812 for paths in self.config.paths.values():
790 result = paths[0] 813 result = paths[0]
791 for pattern in paths[1:]: 814 for pattern in paths[1:]:
792 aliases.add(pattern, result) 815 aliases.add(pattern, result)
793 816
794 self.data_files.combine_parallel_data(self.data, aliases=aliases, data_paths=data_paths) 817 self.data_files.combine_parallel_data(
818 self.data, aliases=aliases, data_paths=data_paths, strict=strict,
819 )
795 820
796 def get_data(self): 821 def get_data(self):
797 """Get the collected data and reset the collector. 822 """Get the collected data.
798 823
799 Also warn about various problems collecting data. 824 Also warn about various problems collecting data.
800 825
801 Returns a :class:`coverage.CoverageData`, the collected coverage data. 826 Returns a :class:`coverage.CoverageData`, the collected coverage data.
802 827
803 .. versionadded:: 4.0 828 .. versionadded:: 4.0
804 829
805 """ 830 """
806 self._init() 831 self._init()
807 if not self._measured: 832
808 return self.data 833 if self.collector.save_data(self.data):
809 834 self._post_save_work()
810 self.collector.save_data(self.data) 835
811 836 return self.data
812 # If there are still entries in the source_pkgs list, then we never 837
813 # encountered those packages. 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.
814 if self._warn_unimported_source: 847 if self._warn_unimported_source:
815 for pkg in self.source_pkgs: 848 for pkg in self.source_pkgs_unmatched:
816 if pkg not in sys.modules: 849 self._warn_about_unmeasured_code(pkg)
817 self._warn("Module %s was never imported." % pkg)
818 elif not (
819 hasattr(sys.modules[pkg], '__file__') and
820 os.path.exists(sys.modules[pkg].__file__)
821 ):
822 self._warn("Module %s has no Python source." % pkg)
823 else:
824 self._warn("Module %s was previously imported, but not measured." % pkg)
825 850
826 # Find out if we got any data. 851 # Find out if we got any data.
827 if not self.data and self._warn_no_data: 852 if not self.data and self._warn_no_data:
828 self._warn("No data was collected.") 853 self._warn("No data was collected.", slug="no-data-collected")
829 854
830 # Find files that were never executed at all. 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 hasattr(sys.modules[pkg], '__file__') or
859 not os.path.exists(sys.modules[pkg].__file__)):
860 continue
861 pkg_file = source_for_file(sys.modules[pkg].__file__)
862 self._find_unexecuted_files(self._canonical_path(pkg_file))
863
831 for src in self.source: 864 for src in self.source:
832 for py_file in find_python_files(src): 865 self._find_unexecuted_files(src)
833 py_file = files.canonical_filename(py_file)
834
835 if self.omit_match and self.omit_match.match(py_file):
836 # Turns out this file was omitted, so don't pull it back
837 # in as unexecuted.
838 continue
839
840 self.data.touch_file(py_file)
841 866
842 if self.config.note: 867 if self.config.note:
843 self.data.add_run_info(note=self.config.note) 868 self.data.add_run_info(note=self.config.note)
844 869
845 self._measured = False 870 def _warn_about_unmeasured_code(self, pkg):
846 return self.data 871 """Warn about a package or module that we never traced.
872
873 `pkg` is a string, the name of the package or module.
874
875 """
876 mod = sys.modules.get(pkg)
877 if mod is None:
878 self._warn("Module %s was never imported." % pkg, slug="module-not-imported")
879 return
880
881 is_namespace = hasattr(mod, '__path__') and not hasattr(mod, '__file__')
882 has_file = hasattr(mod, '__file__') and os.path.exists(mod.__file__)
883
884 if is_namespace:
885 # A namespace package. It's OK for this not to have been traced,
886 # since there is no code directly in it.
887 return
888
889 if not has_file:
890 self._warn("Module %s has no Python source." % pkg, slug="module-not-python")
891 return
892
893 # The module was in sys.modules, and seems like a module with code, but
894 # we never measured it. I guess that means it was imported before
895 # coverage even started.
896 self._warn(
897 "Module %s was previously imported, but not measured" % pkg,
898 slug="module-not-measured",
899 )
900
901 def _find_plugin_files(self, src_dir):
902 """Get executable files from the plugins."""
903 for plugin in self.plugins.file_tracers:
904 for x_file in plugin.find_executable_files(src_dir):
905 yield x_file, plugin._coverage_plugin_name
906
907 def _find_unexecuted_files(self, src_dir):
908 """Find unexecuted files in `src_dir`.
909
910 Search for files in `src_dir` that are probably importable,
911 and add them as unexecuted files in `self.data`.
912
913 """
914 py_files = ((py_file, None) for py_file in find_python_files(src_dir))
915 plugin_files = self._find_plugin_files(src_dir)
916
917 for file_path, plugin_name in itertools.chain(py_files, plugin_files):
918 file_path = canonical_filename(file_path)
919 if self.omit_match and self.omit_match.match(file_path):
920 # Turns out this file was omitted, so don't pull it back
921 # in as unexecuted.
922 continue
923 self.data.touch_file(file_path, plugin_name)
847 924
848 # Backward compatibility with version 1. 925 # Backward compatibility with version 1.
849 def analysis(self, morf): 926 def analysis(self, morf):
850 """Like `analysis2` but doesn't return excluded line numbers.""" 927 """Like `analysis2` but doesn't return excluded line numbers."""
851 f, s, _, m, mf = self.analysis2(morf) 928 f, s, _, m, mf = self.analysis2(morf)
952 1029
953 `include` is a list of file name patterns. Files that match will be 1030 `include` is a list of file name patterns. Files that match will be
954 included in the report. Files matching `omit` will not be included in 1031 included in the report. Files matching `omit` will not be included in
955 the report. 1032 the report.
956 1033
1034 If `skip_covered` is True, don't report on files with 100% coverage.
1035
957 Returns a float, the total percentage covered. 1036 Returns a float, the total percentage covered.
958 1037
959 """ 1038 """
960 self.get_data() 1039 self.get_data()
961 self.config.from_args( 1040 self.config.from_args(
962 ignore_errors=ignore_errors, omit=omit, include=include, 1041 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
963 show_missing=show_missing, skip_covered=skip_covered, 1042 show_missing=show_missing, skip_covered=skip_covered,
964 ) 1043 )
965 reporter = SummaryReporter(self, self.config) 1044 reporter = SummaryReporter(self, self.config)
966 return reporter.report(morfs, outfile=file) 1045 return reporter.report(morfs, outfile=file)
967 1046
979 See :meth:`report` for other arguments. 1058 See :meth:`report` for other arguments.
980 1059
981 """ 1060 """
982 self.get_data() 1061 self.get_data()
983 self.config.from_args( 1062 self.config.from_args(
984 ignore_errors=ignore_errors, omit=omit, include=include 1063 ignore_errors=ignore_errors, report_omit=omit, report_include=include
985 ) 1064 )
986 reporter = AnnotateReporter(self, self.config) 1065 reporter = AnnotateReporter(self, self.config)
987 reporter.report(morfs, directory=directory) 1066 reporter.report(morfs, directory=directory)
988 1067
989 def html_report(self, morfs=None, directory=None, ignore_errors=None, 1068 def html_report(self, morfs=None, directory=None, ignore_errors=None,
990 omit=None, include=None, extra_css=None, title=None): 1069 omit=None, include=None, extra_css=None, title=None,
1070 skip_covered=None):
991 """Generate an HTML report. 1071 """Generate an HTML report.
992 1072
993 The HTML is written to `directory`. The file "index.html" is the 1073 The HTML is written to `directory`. The file "index.html" is the
994 overview starting point, with links to more detailed pages for 1074 overview starting point, with links to more detailed pages for
995 individual modules. 1075 individual modules.
1005 Returns a float, the total percentage covered. 1085 Returns a float, the total percentage covered.
1006 1086
1007 """ 1087 """
1008 self.get_data() 1088 self.get_data()
1009 self.config.from_args( 1089 self.config.from_args(
1010 ignore_errors=ignore_errors, omit=omit, include=include, 1090 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1011 html_dir=directory, extra_css=extra_css, html_title=title, 1091 html_dir=directory, extra_css=extra_css, html_title=title,
1092 skip_covered=skip_covered,
1012 ) 1093 )
1013 reporter = HtmlReporter(self, self.config) 1094 reporter = HtmlReporter(self, self.config)
1014 return reporter.report(morfs) 1095 return reporter.report(morfs)
1015 1096
1016 def xml_report( 1097 def xml_report(
1029 Returns a float, the total percentage covered. 1110 Returns a float, the total percentage covered.
1030 1111
1031 """ 1112 """
1032 self.get_data() 1113 self.get_data()
1033 self.config.from_args( 1114 self.config.from_args(
1034 ignore_errors=ignore_errors, omit=omit, include=include, 1115 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1035 xml_output=outfile, 1116 xml_output=outfile,
1036 ) 1117 )
1037 file_to_close = None 1118 file_to_close = None
1038 delete_file = False 1119 delete_file = False
1039 if self.config.xml_output: 1120 if self.config.xml_output:
1069 1150
1070 import coverage as covmod 1151 import coverage as covmod
1071 1152
1072 self._init() 1153 self._init()
1073 1154
1074 ft_plugins = [] 1155 def plugin_info(plugins):
1075 for ft in self.plugins.file_tracers: 1156 """Make an entry for the sys_info from a list of plug-ins."""
1076 ft_name = ft._coverage_plugin_name 1157 entries = []
1077 if not ft._coverage_enabled: 1158 for plugin in plugins:
1078 ft_name += " (disabled)" 1159 entry = plugin._coverage_plugin_name
1079 ft_plugins.append(ft_name) 1160 if not plugin._coverage_enabled:
1161 entry += " (disabled)"
1162 entries.append(entry)
1163 return entries
1080 1164
1081 info = [ 1165 info = [
1082 ('version', covmod.__version__), 1166 ('version', covmod.__version__),
1083 ('coverage', covmod.__file__), 1167 ('coverage', covmod.__file__),
1084 ('cover_dirs', self.cover_dirs), 1168 ('cover_paths', self.cover_paths),
1085 ('pylib_dirs', self.pylib_dirs), 1169 ('pylib_paths', self.pylib_paths),
1086 ('tracer', self.collector.tracer_name()), 1170 ('tracer', self.collector.tracer_name()),
1087 ('plugins.file_tracers', ft_plugins), 1171 ('plugins.file_tracers', plugin_info(self.plugins.file_tracers)),
1172 ('plugins.configurers', plugin_info(self.plugins.configurers)),
1088 ('config_files', self.config.attempted_config_files), 1173 ('config_files', self.config.attempted_config_files),
1089 ('configs_read', self.config.config_files), 1174 ('configs_read', self.config.config_files),
1090 ('data_path', self.data_files.filename), 1175 ('data_path', self.data_files.filename),
1091 ('python', sys.version.replace('\n', '')), 1176 ('python', sys.version.replace('\n', '')),
1092 ('platform', platform.platform()), 1177 ('platform', platform.platform()),
1183 # already been started, so we can avoid doing it twice. 1268 # already been started, so we can avoid doing it twice.
1184 # 1269 #
1185 # https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy has more 1270 # https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy has more
1186 # details. 1271 # details.
1187 1272
1188 if hasattr(process_startup, "done"): 1273 if hasattr(process_startup, "coverage"):
1189 # We've annotated this function before, so we must have already 1274 # We've annotated this function before, so we must have already
1190 # started coverage.py in this process. Nothing to do. 1275 # started coverage.py in this process. Nothing to do.
1191 return None 1276 return None
1192 1277
1193 process_startup.done = True 1278 cov = Coverage(config_file=cps)
1194 cov = Coverage(config_file=cps, auto_data=True) 1279 process_startup.coverage = cov
1195 cov.start() 1280 cov.start()
1196 cov._warn_no_data = False 1281 cov._warn_no_data = False
1197 cov._warn_unimported_source = False 1282 cov._warn_unimported_source = False
1283 cov._auto_save = True
1198 1284
1199 return cov 1285 return cov
1286
1287
1288 def _prevent_sub_process_measurement():
1289 """Stop any subprocess auto-measurement from writing data."""
1290 auto_created_coverage = getattr(process_startup, "coverage", None)
1291 if auto_created_coverage is not None:
1292 auto_created_coverage._auto_save = False

eric ide

mercurial