DebugClients/Python/coverage/control.py

branch
Py2 comp.
changeset 3495
fac17a82b431
parent 790
2c0ea0163ef4
child 3499
f2d4b02c7e88
equal deleted inserted replaced
3485:f1cbc18f88b2 3495:fac17a82b431
1 """Core control stuff for Coverage.""" 1 """Core control stuff for Coverage."""
2 2
3 import atexit, os, socket 3 import atexit, os, random, socket, sys
4 4
5 from .annotate import AnnotateReporter 5 from .annotate import AnnotateReporter
6 from .backward import string_class # pylint: disable-msg=W0622 6 from .backward import string_class, iitems, sorted # pylint: disable=W0622
7 from .codeunit import code_unit_factory, CodeUnit 7 from .codeunit import code_unit_factory, CodeUnit
8 from .collector import Collector 8 from .collector import Collector
9 from .config import CoverageConfig
9 from .data import CoverageData 10 from .data import CoverageData
10 from .files import FileLocator 11 from .debug import DebugControl
11 from .html import HtmlReporter 12 from .files import FileLocator, TreeMatcher, FnmatchMatcher
12 from .results import Analysis 13 from .files import PathAliases, find_python_files, prep_patterns
14 #from .html import HtmlReporter # Comment for eric5
15 from .misc import CoverageException, bool_or_none, join_regex
16 from .misc import file_be_gone
17 from .results import Analysis, Numbers
13 from .summary import SummaryReporter 18 from .summary import SummaryReporter
14 from .xmlreport import XmlReporter 19 from .xmlreport import XmlReporter
15 20
21 # Pypy has some unusual stuff in the "stdlib". Consider those locations
22 # when deciding where the stdlib is.
23 try:
24 import _structseq # pylint: disable=F0401
25 except ImportError:
26 _structseq = None
27
28
16 class coverage(object): 29 class coverage(object):
17 """Programmatic access to Coverage. 30 """Programmatic access to coverage.py.
18 31
19 To use:: 32 To use::
20 33
21 from . import coverage 34 from . import coverage
22 35
23 cov = coverage() 36 cov = coverage()
24 cov.start() 37 cov.start()
25 #.. blah blah (run your code) blah blah .. 38 #.. call your code ..
26 cov.stop() 39 cov.stop()
27 cov.html_report(directory='covhtml') 40 cov.html_report(directory='covhtml')
28 41
29 """ 42 """
30 43 def __init__(self, data_file=None, data_suffix=None, cover_pylib=None,
31 def __init__(self, data_file=None, data_suffix=False, cover_pylib=False, 44 auto_data=False, timid=None, branch=None, config_file=True,
32 auto_data=False, timid=False, branch=False): 45 source=None, omit=None, include=None, debug=None,
46 debug_file=None):
33 """ 47 """
34 `data_file` is the base name of the data file to use, defaulting to 48 `data_file` is the base name of the data file to use, defaulting to
35 ".coverage". `data_suffix` is appended to `data_file` to create the 49 ".coverage". `data_suffix` is appended (with a dot) to `data_file` to
36 final file name. If `data_suffix` is simply True, then a suffix is 50 create the final file name. If `data_suffix` is simply True, then a
37 created with the machine and process identity included. 51 suffix is created with the machine and process identity included.
38 52
39 `cover_pylib` is a boolean determining whether Python code installed 53 `cover_pylib` is a boolean determining whether Python code installed
40 with the Python interpreter is measured. This includes the Python 54 with the Python interpreter is measured. This includes the Python
41 standard library and any packages installed with the interpreter. 55 standard library and any packages installed with the interpreter.
42 56
49 tracing functions breaks the faster trace function. 63 tracing functions breaks the faster trace function.
50 64
51 If `branch` is true, then branch coverage will be measured in addition 65 If `branch` is true, then branch coverage will be measured in addition
52 to the usual statement coverage. 66 to the usual statement coverage.
53 67
68 `config_file` determines what config file to read. If it is a string,
69 it is the name of the config file to read. If it is True, then a
70 standard file is read (".coveragerc"). If it is False, then no file is
71 read.
72
73 `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
75 measured.
76
77 `include` and `omit` are lists of filename patterns. Files that match
78 `include` will be measured, files that match `omit` will not. Each
79 will also accept a single string argument.
80
81 `debug` is a list of strings indicating what debugging information is
82 desired. `debug_file` is the file to write debug messages to,
83 defaulting to stderr.
84
54 """ 85 """
55 from . import __version__ 86 from . import __version__
56 87
57 self.cover_pylib = cover_pylib 88 # A record of all the warnings that have been issued.
89 self._warnings = []
90
91 # Build our configuration from a number of sources:
92 # 1: defaults:
93 self.config = CoverageConfig()
94
95 # 2: from the coveragerc file:
96 if config_file:
97 if config_file is True:
98 config_file = ".coveragerc"
99 try:
100 self.config.from_file(config_file)
101 except ValueError:
102 _, err, _ = sys.exc_info()
103 raise CoverageException(
104 "Couldn't read config file %s: %s" % (config_file, err)
105 )
106
107 # 3: from environment variables:
108 self.config.from_environment('COVERAGE_OPTIONS')
109 env_data_file = os.environ.get('COVERAGE_FILE')
110 if env_data_file:
111 self.config.data_file = env_data_file
112
113 # 4: from constructor arguments:
114 self.config.from_args(
115 data_file=data_file, cover_pylib=cover_pylib, timid=timid,
116 branch=branch, parallel=bool_or_none(data_suffix),
117 source=source, omit=omit, include=include, debug=debug,
118 )
119
120 # Create and configure the debugging controller.
121 self.debug = DebugControl(self.config.debug, debug_file or sys.stderr)
122
58 self.auto_data = auto_data 123 self.auto_data = auto_data
59 self.atexit_registered = False 124
60 125 # _exclude_re is a dict mapping exclusion list names to compiled
61 self.exclude_re = "" 126 # regexes.
62 self.exclude_list = [] 127 self._exclude_re = {}
128 self._exclude_regex_stale()
63 129
64 self.file_locator = FileLocator() 130 self.file_locator = FileLocator()
65 131
66 # Timidity: for nose users, read an environment variable. This is a 132 # The source argument can be directories or package names.
67 # cheap hack, since the rest of the command line arguments aren't 133 self.source = []
68 # recognized, but it solves some users' problems. 134 self.source_pkgs = []
69 timid = timid or ('--timid' in os.environ.get('COVERAGE_OPTIONS', '')) 135 for src in self.config.source or []:
136 if os.path.exists(src):
137 self.source.append(self.file_locator.canonical_filename(src))
138 else:
139 self.source_pkgs.append(src)
140
141 self.omit = prep_patterns(self.config.omit)
142 self.include = prep_patterns(self.config.include)
143
70 self.collector = Collector( 144 self.collector = Collector(
71 self._should_trace, timid=timid, branch=branch 145 self._should_trace, timid=self.config.timid,
146 branch=self.config.branch, warn=self._warn
72 ) 147 )
73 148
74 # Create the data file. 149 # Suffixes are a bit tricky. We want to use the data suffix only when
75 if data_suffix: 150 # 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
152 # find that we are collecting data later.
153 if data_suffix or self.config.parallel:
76 if not isinstance(data_suffix, string_class): 154 if not isinstance(data_suffix, string_class):
77 # if data_suffix=True, use .machinename.pid 155 # if data_suffix=True, use .machinename.pid.random
78 data_suffix = ".%s.%s" % (socket.gethostname(), os.getpid()) 156 data_suffix = True
79 else: 157 else:
80 data_suffix = None 158 data_suffix = None
81 159 self.data_suffix = None
160 self.run_suffix = data_suffix
161
162 # 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
164 # started rather than wherever the process eventually chdir'd to.
82 self.data = CoverageData( 165 self.data = CoverageData(
83 basename=data_file, suffix=data_suffix, 166 basename=self.config.data_file,
84 collector="coverage v%s" % __version__ 167 collector="coverage v%s" % __version__,
168 debug=self.debug,
85 ) 169 )
86 170
87 # The default exclude pattern. 171 # The dirs for files considered "installed with the interpreter".
88 self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]') 172 self.pylib_dirs = []
89 173 if not self.config.cover_pylib:
90 # The prefix for files considered "installed with the interpreter". 174 # Look at where some standard modules are located. That's the
91 if not self.cover_pylib: 175 # indication for "installed with the interpreter". In some
92 # Look at where the "os" module is located. That's the indication 176 # environments (virtualenv, for example), these modules may be
93 # for "installed with the interpreter". 177 # spread across a few locations. Look at all the candidate modules
94 os_file = self.file_locator.canonical_filename(os.__file__) 178 # we've imported, and take all the different ones.
95 self.pylib_prefix = os.path.split(os_file)[0] 179 for m in (atexit, os, random, socket, _structseq):
180 if m is not None and hasattr(m, "__file__"):
181 m_dir = self._canonical_dir(m)
182 if m_dir not in self.pylib_dirs:
183 self.pylib_dirs.append(m_dir)
96 184
97 # To avoid tracing the coverage code itself, we skip anything located 185 # To avoid tracing the coverage code itself, we skip anything located
98 # where we are. 186 # where we are.
99 here = self.file_locator.canonical_filename(__file__) 187 self.cover_dir = self._canonical_dir(__file__)
100 self.cover_prefix = os.path.split(here)[0] 188
101 189 # The matchers for _should_trace.
102 def _should_trace(self, filename, frame): 190 self.source_match = None
103 """Decide whether to trace execution in `filename` 191 self.pylib_match = self.cover_match = None
192 self.include_match = self.omit_match = None
193
194 # Set the reporting precision.
195 Numbers.set_precision(self.config.precision)
196
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)
208
209 def _canonical_dir(self, morf):
210 """Return the canonical directory of the module or file `morf`."""
211 return os.path.split(CodeUnit(morf, self.file_locator).filename)[0]
212
213 def _source_for_file(self, filename):
214 """Return the source file for `filename`."""
215 if not filename.endswith(".py"):
216 if filename.endswith((".pyc", ".pyo")):
217 filename = filename[:-1]
218 elif filename.endswith("$py.class"): # jython
219 filename = filename[:-9] + ".py"
220 return filename
221
222 def _should_trace_with_reason(self, filename, frame):
223 """Decide whether to trace execution in `filename`, with a reason.
104 224
105 This function is called from the trace function. As each new file name 225 This function is called from the trace function. As each new file name
106 is encountered, this function determines whether it is traced or not. 226 is encountered, this function determines whether it is traced or not.
107 227
108 Returns a canonicalized filename if it should be traced, False if it 228 Returns a pair of values: the first indicates whether the file should
109 should not. 229 be traced: it's a canonicalized filename if it should be traced, None
110 230 if it should not. The second value is a string, the resason for the
111 """ 231 decision.
112 if filename == '<string>': 232
113 # There's no point in ever tracing string executions, we can't do 233 """
114 # anything with the data later anyway. 234 if not filename:
115 return False 235 # Empty string is pretty useless
236 return None, "empty string isn't a filename"
237
238 if filename.startswith('<'):
239 # Lots of non-file execution is represented with artificial
240 # filenames like "<string>", "<doctest readme.txt[0]>", or
241 # "<exec_function>". Don't ever trace these executions, since we
242 # can't do anything with the data later anyway.
243 return None, "not a real filename"
244
245 self._check_for_packages()
116 246
117 # Compiled Python files have two filenames: frame.f_code.co_filename is 247 # Compiled Python files have two filenames: frame.f_code.co_filename is
118 # the filename at the time the .pyc was compiled. The second name 248 # the filename at the time the .pyc was compiled. The second name is
119 # is __file__, which is where the .pyc was actually loaded from. Since 249 # __file__, which is where the .pyc was actually loaded from. Since
120 # .pyc files can be moved after compilation (for example, by being 250 # .pyc files can be moved after compilation (for example, by being
121 # installed), we look for __file__ in the frame and prefer it to the 251 # installed), we look for __file__ in the frame and prefer it to the
122 # co_filename value. 252 # co_filename value.
123 dunder_file = frame.f_globals.get('__file__') 253 dunder_file = frame.f_globals.get('__file__')
124 if dunder_file: 254 if dunder_file:
125 if not dunder_file.endswith(".py"): 255 filename = self._source_for_file(dunder_file)
126 if dunder_file[-4:-1] == ".py": 256
127 dunder_file = dunder_file[:-1] 257 # Jython reports the .class file to the tracer, use the source file.
128 filename = dunder_file 258 if filename.endswith("$py.class"):
259 filename = filename[:-9] + ".py"
129 260
130 canonical = self.file_locator.canonical_filename(filename) 261 canonical = self.file_locator.canonical_filename(filename)
131 262
132 # If we aren't supposed to trace installed code, then check if this is 263 # If the user specified source or include, then that's authoritative
133 # near the Python standard library and skip it if so. 264 # about the outer bound of what to measure and we don't have to apply
134 if not self.cover_pylib: 265 # any canned exclusions. If they didn't, then we have to exclude the
135 if canonical.startswith(self.pylib_prefix): 266 # stdlib and coverage.py directories.
136 return False 267 if self.source_match:
137 268 if not self.source_match.match(canonical):
138 # We exclude the coverage code itself, since a little of it will be 269 return None, "falls outside the --source trees"
139 # measured otherwise. 270 elif self.include_match:
140 if canonical.startswith(self.cover_prefix): 271 if not self.include_match.match(canonical):
141 return False 272 return None, "falls outside the --include trees"
142 273 else:
274 # 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.
276 if self.pylib_match and self.pylib_match.match(canonical):
277 return None, "is in the stdlib"
278
279 # We exclude the coverage code itself, since a little of it will be
280 # measured otherwise.
281 if self.cover_match and self.cover_match.match(canonical):
282 return None, "is part of coverage.py"
283
284 # Check the file against the omit pattern.
285 if self.omit_match and self.omit_match.match(canonical):
286 return None, "is inside an --omit pattern"
287
288 return canonical, "because we love you"
289
290 def _should_trace(self, filename, frame):
291 """Decide whether to trace execution in `filename`.
292
293 Calls `_should_trace_with_reason`, and returns just the decision.
294
295 """
296 canonical, reason = self._should_trace_with_reason(filename, frame)
297 if self.debug.should('trace'):
298 if not canonical:
299 msg = "Not tracing %r: %s" % (filename, reason)
300 else:
301 msg = "Tracing %r" % (filename,)
302 self.debug.write(msg)
143 return canonical 303 return canonical
144 304
145 # To log what should_trace returns, change this to "if 1:" 305 def _warn(self, msg):
146 if 0: 306 """Use `msg` as a warning."""
147 _real_should_trace = _should_trace 307 self._warnings.append(msg)
148 def _should_trace(self, filename, frame): # pylint: disable-msg=E0102 308 sys.stderr.write("Coverage.py warning: %s\n" % msg)
149 """A logging decorator around the real _should_trace function.""" 309
150 ret = self._real_should_trace(filename, frame) 310 def _check_for_packages(self):
151 print("should_trace: %r -> %r" % (filename, ret)) 311 """Update the source_match matcher with latest imported packages."""
152 return ret 312 # Our self.source_pkgs attribute is a list of package names we want to
313 # measure. Each time through here, we see if we've imported any of
314 # them yet. If so, we add its file to source_match, and we don't have
315 # to look for that package any more.
316 if self.source_pkgs:
317 found = []
318 for pkg in self.source_pkgs:
319 try:
320 mod = sys.modules[pkg]
321 except KeyError:
322 continue
323
324 found.append(pkg)
325
326 try:
327 pkg_file = mod.__file__
328 except AttributeError:
329 pkg_file = None
330 else:
331 d, f = os.path.split(pkg_file)
332 if f.startswith('__init__'):
333 # This is actually a package, return the directory.
334 pkg_file = d
335 else:
336 pkg_file = self._source_for_file(pkg_file)
337 pkg_file = self.file_locator.canonical_filename(pkg_file)
338 if not os.path.exists(pkg_file):
339 pkg_file = None
340
341 if pkg_file:
342 self.source.append(pkg_file)
343 self.source_match.add(pkg_file)
344 else:
345 self._warn("Module %s has no Python source." % pkg)
346
347 for pkg in found:
348 self.source_pkgs.remove(pkg)
153 349
154 def use_cache(self, usecache): 350 def use_cache(self, usecache):
155 """Control the use of a data file (incorrectly called a cache). 351 """Control the use of a data file (incorrectly called a cache).
156 352
157 `usecache` is true or false, whether to read and write data on disk. 353 `usecache` is true or false, whether to read and write data on disk.
163 """Load previously-collected coverage data from the data file.""" 359 """Load previously-collected coverage data from the data file."""
164 self.collector.reset() 360 self.collector.reset()
165 self.data.read() 361 self.data.read()
166 362
167 def start(self): 363 def start(self):
168 """Start measuring code coverage.""" 364 """Start measuring code coverage.
365
366 Coverage measurement actually occurs in functions called after `start`
367 is invoked. Statements in the same scope as `start` won't be measured.
368
369 Once you invoke `start`, you must also call `stop` eventually, or your
370 process might not shut down cleanly.
371
372 """
373 if self.run_suffix:
374 # Calling start() means we're running code, so use the run_suffix
375 # as the data_suffix when we eventually save the data.
376 self.data_suffix = self.run_suffix
169 if self.auto_data: 377 if self.auto_data:
170 self.load() 378 self.load()
171 # Save coverage data when Python exits. 379
172 if not self.atexit_registered: 380 # Create the matchers we need for _should_trace
173 atexit.register(self.save) 381 if self.source or self.source_pkgs:
174 self.atexit_registered = True 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
175 self.collector.start() 403 self.collector.start()
404 self._started = True
405 self._measured = True
176 406
177 def stop(self): 407 def stop(self):
178 """Stop measuring code coverage.""" 408 """Stop measuring code coverage."""
409 self._started = False
179 self.collector.stop() 410 self.collector.stop()
180 self._harvest_data() 411
412 def _atexit(self):
413 """Clean up on process shutdown."""
414 if self._started:
415 self.stop()
416 if self.auto_data:
417 self.save()
181 418
182 def erase(self): 419 def erase(self):
183 """Erase previously-collected coverage data. 420 """Erase previously-collected coverage data.
184 421
185 This removes the in-memory data collected in this session as well as 422 This removes the in-memory data collected in this session as well as
187 424
188 """ 425 """
189 self.collector.reset() 426 self.collector.reset()
190 self.data.erase() 427 self.data.erase()
191 428
192 def clear_exclude(self): 429 def clear_exclude(self, which='exclude'):
193 """Clear the exclude list.""" 430 """Clear the exclude list."""
194 self.exclude_list = [] 431 setattr(self.config, which + "_list", [])
195 self.exclude_re = "" 432 self._exclude_regex_stale()
196 433
197 def exclude(self, regex): 434 def exclude(self, regex, which='exclude'):
198 """Exclude source lines from execution consideration. 435 """Exclude source lines from execution consideration.
199 436
200 `regex` is a regular expression. Lines matching this expression are 437 A number of lists of regular expressions are maintained. Each list
201 not considered executable when reporting code coverage. A list of 438 selects lines that are treated differently during reporting.
202 regexes is maintained; this function adds a new regex to the list. 439
203 Matching any of the regexes excludes a source line. 440 `which` determines which list is modified. The "exclude" list selects
204 441 lines that are not considered executable at all. The "partial" list
205 """ 442 indicates lines with branches that are not taken.
206 self.exclude_list.append(regex) 443
207 self.exclude_re = "(" + ")|(".join(self.exclude_list) + ")" 444 `regex` is a regular expression. The regex is added to the specified
208 445 list. If any of the regexes in the list is found in a line, the line
209 def get_exclude_list(self): 446 is marked for special treatment during reporting.
210 """Return the list of excluded regex patterns.""" 447
211 return self.exclude_list 448 """
449 excl_list = getattr(self.config, which + "_list")
450 excl_list.append(regex)
451 self._exclude_regex_stale()
452
453 def _exclude_regex_stale(self):
454 """Drop all the compiled exclusion regexes, a list was modified."""
455 self._exclude_re.clear()
456
457 def _exclude_regex(self, which):
458 """Return a compiled regex for the given exclusion list."""
459 if which not in self._exclude_re:
460 excl_list = getattr(self.config, which + "_list")
461 self._exclude_re[which] = join_regex(excl_list)
462 return self._exclude_re[which]
463
464 def get_exclude_list(self, which='exclude'):
465 """Return a list of excluded regex patterns.
466
467 `which` indicates which list is desired. See `exclude` for the lists
468 that are available, and their meaning.
469
470 """
471 return getattr(self.config, which + "_list")
212 472
213 def save(self): 473 def save(self):
214 """Save the collected coverage data to the data file.""" 474 """Save the collected coverage data to the data file."""
475 data_suffix = self.data_suffix
476 if data_suffix is True:
477 # If data_suffix was a simple true value, then make a suffix with
478 # plenty of distinguishing information. We do this here in
479 # `save()` at the last minute so that the pid will be correct even
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
215 self._harvest_data() 492 self._harvest_data()
216 self.data.write() 493 self.data.write(suffix=data_suffix)
217 494
218 def combine(self): 495 def combine(self):
219 """Combine together a number of similarly-named coverage data files. 496 """Combine together a number of similarly-named coverage data files.
220 497
221 All coverage data files whose name starts with `data_file` (from the 498 All coverage data files whose name starts with `data_file` (from the
222 coverage() constructor) will be read, and combined together into the 499 coverage() constructor) will be read, and combined together into the
223 current measurements. 500 current measurements.
224 501
225 """ 502 """
226 self.data.combine_parallel_data() 503 aliases = None
504 if self.config.paths:
505 aliases = PathAliases(self.file_locator)
506 for paths in self.config.paths.values():
507 result = paths[0]
508 for pattern in paths[1:]:
509 aliases.add(pattern, result)
510 self.data.combine_parallel_data(aliases=aliases)
227 511
228 def _harvest_data(self): 512 def _harvest_data(self):
229 """Get the collected data and reset the collector.""" 513 """Get the collected data and reset the collector.
514
515 Also warn about various problems collecting data.
516
517 """
518 if not self._measured:
519 return
520
230 self.data.add_line_data(self.collector.get_line_data()) 521 self.data.add_line_data(self.collector.get_line_data())
231 self.data.add_arc_data(self.collector.get_arc_data()) 522 self.data.add_arc_data(self.collector.get_arc_data())
232 self.collector.reset() 523 self.collector.reset()
524
525 # If there are still entries in the source_pkgs list, then we never
526 # encountered those packages.
527 if self._warn_unimported_source:
528 for pkg in self.source_pkgs:
529 self._warn("Module %s was never imported." % pkg)
530
531 # Find out if we got any data.
532 summary = self.data.summary()
533 if not summary and self._warn_no_data:
534 self._warn("No data was collected.")
535
536 # Find files that were never executed at all.
537 for src in self.source:
538 for py_file in find_python_files(src):
539 py_file = self.file_locator.canonical_filename(py_file)
540
541 if self.omit_match and self.omit_match.match(py_file):
542 # Turns out this file was omitted, so don't pull it back
543 # in as unexecuted.
544 continue
545
546 self.data.touch_file(py_file)
547
548 self._measured = False
233 549
234 # Backward compatibility with version 1. 550 # Backward compatibility with version 1.
235 def analysis(self, morf): 551 def analysis(self, morf):
236 """Like `analysis2` but doesn't return excluded line numbers.""" 552 """Like `analysis2` but doesn't return excluded line numbers."""
237 f, s, _, m, mf = self.analysis2(morf) 553 f, s, _, m, mf = self.analysis2(morf)
254 coverage data. 570 coverage data.
255 571
256 """ 572 """
257 analysis = self._analyze(morf) 573 analysis = self._analyze(morf)
258 return ( 574 return (
259 analysis.filename, analysis.statements, analysis.excluded, 575 analysis.filename,
260 analysis.missing, analysis.missing_formatted() 576 sorted(analysis.statements),
577 sorted(analysis.excluded),
578 sorted(analysis.missing),
579 analysis.missing_formatted(),
261 ) 580 )
262 581
263 def _analyze(self, it): 582 def _analyze(self, it):
264 """Analyze a single morf or code unit. 583 """Analyze a single morf or code unit.
265 584
266 Returns an `Analysis` object. 585 Returns an `Analysis` object.
267 586
268 """ 587 """
588 self._harvest_data()
269 if not isinstance(it, CodeUnit): 589 if not isinstance(it, CodeUnit):
270 it = code_unit_factory(it, self.file_locator)[0] 590 it = code_unit_factory(it, self.file_locator)[0]
271 591
272 return Analysis(self, it) 592 return Analysis(self, it)
273 593
274 def report(self, morfs=None, show_missing=True, ignore_errors=False, 594 def report(self, morfs=None, show_missing=True, ignore_errors=None,
275 file=None, omit_prefixes=None): # pylint: disable-msg=W0622 595 file=None, # pylint: disable=W0622
596 omit=None, include=None
597 ):
276 """Write a summary report to `file`. 598 """Write a summary report to `file`.
277 599
278 Each module in `morfs` is listed, with counts of statements, executed 600 Each module in `morfs` is listed, with counts of statements, executed
279 statements, missing statements, and a list of lines missed. 601 statements, missing statements, and a list of lines missed.
280 602
281 """ 603 `include` is a list of filename patterns. Modules whose filenames
282 reporter = SummaryReporter(self, show_missing, ignore_errors) 604 match those patterns will be included in the report. Modules matching
283 reporter.report(morfs, outfile=file, omit_prefixes=omit_prefixes) 605 `omit` will not be included in the report.
284 606
285 def annotate(self, morfs=None, directory=None, ignore_errors=False, 607 Returns a float, the total percentage covered.
286 omit_prefixes=None): 608
609 """
610 self._harvest_data()
611 self.config.from_args(
612 ignore_errors=ignore_errors, omit=omit, include=include,
613 show_missing=show_missing,
614 )
615 reporter = SummaryReporter(self, self.config)
616 return reporter.report(morfs, outfile=file)
617
618 def annotate(self, morfs=None, directory=None, ignore_errors=None,
619 omit=None, include=None):
287 """Annotate a list of modules. 620 """Annotate a list of modules.
288 621
289 Each module in `morfs` is annotated. The source is written to a new 622 Each module in `morfs` is annotated. The source is written to a new
290 file, named with a ",cover" suffix, with each line prefixed with a 623 file, named with a ",cover" suffix, with each line prefixed with a
291 marker to indicate the coverage of the line. Covered lines have ">", 624 marker to indicate the coverage of the line. Covered lines have ">",
292 excluded lines have "-", and missing lines have "!". 625 excluded lines have "-", and missing lines have "!".
293 626
294 """ 627 See `coverage.report()` for other arguments.
295 reporter = AnnotateReporter(self, ignore_errors) 628
296 reporter.report( 629 """
297 morfs, directory=directory, omit_prefixes=omit_prefixes) 630 self._harvest_data()
298 631 self.config.from_args(
299 def html_report(self, morfs=None, directory=None, ignore_errors=False, 632 ignore_errors=ignore_errors, omit=omit, include=include
300 omit_prefixes=None): 633 )
634 reporter = AnnotateReporter(self, self.config)
635 reporter.report(morfs, directory=directory)
636
637 def html_report(self, morfs=None, directory=None, ignore_errors=None,
638 omit=None, include=None, extra_css=None, title=None):
301 """Generate an HTML report. 639 """Generate an HTML report.
302 640
303 """ 641 The HTML is written to `directory`. The file "index.html" is the
304 reporter = HtmlReporter(self, ignore_errors) 642 overview starting point, with links to more detailed pages for
305 reporter.report( 643 individual modules.
306 morfs, directory=directory, omit_prefixes=omit_prefixes) 644
307 645 `extra_css` is a path to a file of other CSS to apply on the page.
308 def xml_report(self, morfs=None, outfile=None, ignore_errors=False, 646 It will be copied into the HTML directory.
309 omit_prefixes=None): 647
648 `title` is a text string (not HTML) to use as the title of the HTML
649 report.
650
651 See `coverage.report()` for other arguments.
652
653 Returns a float, the total percentage covered.
654
655 """
656 self._harvest_data()
657 self.config.from_args(
658 ignore_errors=ignore_errors, omit=omit, include=include,
659 html_dir=directory, extra_css=extra_css, html_title=title,
660 )
661 reporter = HtmlReporter(self, self.config)
662 return reporter.report(morfs)
663
664 def xml_report(self, morfs=None, outfile=None, ignore_errors=None,
665 omit=None, include=None):
310 """Generate an XML report of coverage results. 666 """Generate an XML report of coverage results.
311 667
312 The report is compatible with Cobertura reports. 668 The report is compatible with Cobertura reports.
313 669
314 """ 670 Each module in `morfs` is included in the report. `outfile` is the
315 if outfile: 671 path to write the file to, "-" will write to stdout.
316 outfile = open(outfile, "w") 672
673 See `coverage.report()` for other arguments.
674
675 Returns a float, the total percentage covered.
676
677 """
678 self._harvest_data()
679 self.config.from_args(
680 ignore_errors=ignore_errors, omit=omit, include=include,
681 xml_output=outfile,
682 )
683 file_to_close = None
684 delete_file = False
685 if self.config.xml_output:
686 if self.config.xml_output == '-':
687 outfile = sys.stdout
688 else:
689 outfile = open(self.config.xml_output, "w")
690 file_to_close = outfile
317 try: 691 try:
318 reporter = XmlReporter(self, ignore_errors) 692 try:
319 reporter.report( 693 reporter = XmlReporter(self, self.config)
320 morfs, omit_prefixes=omit_prefixes, outfile=outfile) 694 return reporter.report(morfs, outfile=outfile)
695 except CoverageException:
696 delete_file = True
697 raise
321 finally: 698 finally:
322 outfile.close() 699 if file_to_close:
700 file_to_close.close()
701 if delete_file:
702 file_be_gone(self.config.xml_output)
323 703
324 def sysinfo(self): 704 def sysinfo(self):
325 """Return a list of key,value pairs showing internal information.""" 705 """Return a list of (key, value) pairs showing internal information."""
326 706
327 import coverage as covmod 707 import coverage as covmod
328 import platform, re, sys 708 import platform, re
709
710 try:
711 implementation = platform.python_implementation()
712 except AttributeError:
713 implementation = "unknown"
329 714
330 info = [ 715 info = [
331 ('version', covmod.__version__), 716 ('version', covmod.__version__),
332 ('coverage', covmod.__file__), 717 ('coverage', covmod.__file__),
333 ('cover_prefix', self.cover_prefix), 718 ('cover_dir', self.cover_dir),
334 ('pylib_prefix', self.pylib_prefix), 719 ('pylib_dirs', self.pylib_dirs),
335 ('tracer', self.collector.tracer_name()), 720 ('tracer', self.collector.tracer_name()),
721 ('config_files', self.config.attempted_config_files),
722 ('configs_read', self.config.config_files),
336 ('data_path', self.data.filename), 723 ('data_path', self.data.filename),
337 ('python', sys.version.replace('\n', '')), 724 ('python', sys.version.replace('\n', '')),
338 ('platform', platform.platform()), 725 ('platform', platform.platform()),
726 ('implementation', implementation),
727 ('executable', sys.executable),
339 ('cwd', os.getcwd()), 728 ('cwd', os.getcwd()),
340 ('path', sys.path), 729 ('path', sys.path),
341 ('environment', [ 730 ('environment', sorted([
342 ("%s = %s" % (k, v)) for k, v in os.environ.items() 731 ("%s = %s" % (k, v)) for k, v in iitems(os.environ)
343 if re.search("^COV|^PY", k) 732 if re.search(r"^COV|^PY", k)
344 ]), 733 ])),
734 ('command_line', " ".join(getattr(sys, 'argv', ['???']))),
345 ] 735 ]
736 if self.source_match:
737 info.append(('source_match', self.source_match.info()))
738 if self.include_match:
739 info.append(('include_match', self.include_match.info()))
740 if self.omit_match:
741 info.append(('omit_match', self.omit_match.info()))
742 if self.cover_match:
743 info.append(('cover_match', self.cover_match.info()))
744 if self.pylib_match:
745 info.append(('pylib_match', self.pylib_match.info()))
746
346 return info 747 return info
347 748
348 # 749
349 # eflag: FileType = Python2 750 def process_startup():
751 """Call this at Python startup to perhaps measure coverage.
752
753 If the environment variable COVERAGE_PROCESS_START is defined, coverage
754 measurement is started. The value of the variable is the config file
755 to use.
756
757 There are two ways to configure your Python installation to invoke this
758 function when Python starts:
759
760 #. Create or append to sitecustomize.py to add these lines::
761
762 import coverage
763 coverage.process_startup()
764
765 #. Create a .pth file in your Python installation containing::
766
767 import coverage; coverage.process_startup()
768
769 """
770 cps = os.environ.get("COVERAGE_PROCESS_START")
771 if cps:
772 cov = coverage(config_file=cps, auto_data=True)
773 cov.start()
774 cov._warn_no_data = False
775 cov._warn_unimported_source = False
776
777
778 # A hack for debugging testing in subprocesses.
779 _TEST_NAME_FILE = "" #"/tmp/covtest.txt"

eric ide

mercurial