blob: 5ca1ef9538cc2e3c291b7c95d487501391be17a9 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001"""Core control stuff for Coverage."""
2
3import atexit, os, random, socket, sys
4
5from coverage.annotate import AnnotateReporter
6from coverage.backward import string_class
7from coverage.codeunit import code_unit_factory, CodeUnit
8from coverage.collector import Collector
9from coverage.config import CoverageConfig
10from coverage.data import CoverageData
11from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher
12from coverage.files import find_python_files
13from coverage.html import HtmlReporter
14from coverage.misc import CoverageException, bool_or_none, join_regex
15from coverage.results import Analysis, Numbers
16from coverage.summary import SummaryReporter
17from coverage.xmlreport import XmlReporter
18
19class coverage(object):
20 """Programmatic access to Coverage.
21
22 To use::
23
24 from coverage import coverage
25
26 cov = coverage()
27 cov.start()
28 #.. blah blah (run your code) blah blah ..
29 cov.stop()
30 cov.html_report(directory='covhtml')
31
32 """
33 def __init__(self, data_file=None, data_suffix=None, cover_pylib=None,
34 auto_data=False, timid=None, branch=None, config_file=True,
35 source=None, omit=None, include=None):
36 """
37 `data_file` is the base name of the data file to use, defaulting to
38 ".coverage". `data_suffix` is appended (with a dot) to `data_file` to
39 create the final file name. If `data_suffix` is simply True, then a
40 suffix is created with the machine and process identity included.
41
42 `cover_pylib` is a boolean determining whether Python code installed
43 with the Python interpreter is measured. This includes the Python
44 standard library and any packages installed with the interpreter.
45
46 If `auto_data` is true, then any existing data file will be read when
47 coverage measurement starts, and data will be saved automatically when
48 measurement stops.
49
50 If `timid` is true, then a slower and simpler trace function will be
51 used. This is important for some environments where manipulation of
52 tracing functions breaks the faster trace function.
53
54 If `branch` is true, then branch coverage will be measured in addition
55 to the usual statement coverage.
56
57 `config_file` determines what config file to read. If it is a string,
58 it is the name of the config file to read. If it is True, then a
59 standard file is read (".coveragerc"). If it is False, then no file is
60 read.
61
62 `source` is a list of file paths or package names. Only code located
63 in the trees indicated by the file paths or package names will be
64 measured.
65
66 `include` and `omit` are lists of filename patterns. Files that match
67 `include` will be measured, files that match `omit` will not. Each
68 will also accept a single string argument.
69
70 """
71 from coverage import __version__
72
73 # A record of all the warnings that have been issued.
74 self._warnings = []
75
76 # Build our configuration from a number of sources:
77 # 1: defaults:
78 self.config = CoverageConfig()
79
80 # 2: from the coveragerc file:
81 if config_file:
82 if config_file is True:
83 config_file = ".coveragerc"
84 try:
85 self.config.from_file(config_file)
86 except ValueError:
87 _, err, _ = sys.exc_info()
88 raise CoverageException(
89 "Couldn't read config file %s: %s" % (config_file, err)
90 )
91
92 # 3: from environment variables:
93 self.config.from_environment('COVERAGE_OPTIONS')
94 env_data_file = os.environ.get('COVERAGE_FILE')
95 if env_data_file:
96 self.config.data_file = env_data_file
97
98 # 4: from constructor arguments:
99 if isinstance(omit, string_class):
100 omit = [omit]
101 if isinstance(include, string_class):
102 include = [include]
103 self.config.from_args(
104 data_file=data_file, cover_pylib=cover_pylib, timid=timid,
105 branch=branch, parallel=bool_or_none(data_suffix),
106 source=source, omit=omit, include=include
107 )
108
109 self.auto_data = auto_data
110 self.atexit_registered = False
111
112 # _exclude_re is a dict mapping exclusion list names to compiled
113 # regexes.
114 self._exclude_re = {}
115 self._exclude_regex_stale()
116
117 self.file_locator = FileLocator()
118
119 # The source argument can be directories or package names.
120 self.source = []
121 self.source_pkgs = []
122 for src in self.config.source or []:
123 if os.path.exists(src):
124 self.source.append(self.file_locator.canonical_filename(src))
125 else:
126 self.source_pkgs.append(src)
127
128 self.omit = self._prep_patterns(self.config.omit)
129 self.include = self._prep_patterns(self.config.include)
130
131 self.collector = Collector(
132 self._should_trace, timid=self.config.timid,
133 branch=self.config.branch, warn=self._warn
134 )
135
136 # Suffixes are a bit tricky. We want to use the data suffix only when
137 # collecting data, not when combining data. So we save it as
138 # `self.run_suffix` now, and promote it to `self.data_suffix` if we
139 # find that we are collecting data later.
140 if data_suffix or self.config.parallel:
141 if not isinstance(data_suffix, string_class):
142 # if data_suffix=True, use .machinename.pid.random
143 data_suffix = True
144 else:
145 data_suffix = None
146 self.data_suffix = None
147 self.run_suffix = data_suffix
148
149 # Create the data file. We do this at construction time so that the
150 # data file will be written into the directory where the process
151 # started rather than wherever the process eventually chdir'd to.
152 self.data = CoverageData(
153 basename=self.config.data_file,
154 collector="coverage v%s" % __version__
155 )
156
157 # The dirs for files considered "installed with the interpreter".
158 self.pylib_dirs = []
159 if not self.config.cover_pylib:
160 # Look at where some standard modules are located. That's the
161 # indication for "installed with the interpreter". In some
162 # environments (virtualenv, for example), these modules may be
163 # spread across a few locations. Look at all the candidate modules
164 # we've imported, and take all the different ones.
165 for m in (atexit, os, random, socket):
166 if hasattr(m, "__file__"):
167 m_dir = self._canonical_dir(m.__file__)
168 if m_dir not in self.pylib_dirs:
169 self.pylib_dirs.append(m_dir)
170
171 # To avoid tracing the coverage code itself, we skip anything located
172 # where we are.
173 self.cover_dir = self._canonical_dir(__file__)
174
175 # The matchers for _should_trace, created when tracing starts.
176 self.source_match = None
177 self.pylib_match = self.cover_match = None
178 self.include_match = self.omit_match = None
179
180 # Only _harvest_data once per measurement cycle.
181 self._harvested = False
182
183 # Set the reporting precision.
184 Numbers.set_precision(self.config.precision)
185
186 # When tearing down the coverage object, modules can become None.
187 # Saving the modules as object attributes avoids problems, but it is
188 # quite ad-hoc which modules need to be saved and which references
189 # need to use the object attributes.
190 self.socket = socket
191 self.os = os
192 self.random = random
193
194 def _canonical_dir(self, f):
195 """Return the canonical directory of the file `f`."""
196 return os.path.split(self.file_locator.canonical_filename(f))[0]
197
198 def _source_for_file(self, filename):
199 """Return the source file for `filename`."""
200 if not filename.endswith(".py"):
201 if filename[-4:-1] == ".py":
202 filename = filename[:-1]
203 return filename
204
205 def _should_trace(self, filename, frame):
206 """Decide whether to trace execution in `filename`
207
208 This function is called from the trace function. As each new file name
209 is encountered, this function determines whether it is traced or not.
210
211 Returns a canonicalized filename if it should be traced, False if it
212 should not.
213
214 """
215 if os is None:
216 return False
217
218 if filename.startswith('<'):
219 # Lots of non-file execution is represented with artificial
220 # filenames like "<string>", "<doctest readme.txt[0]>", or
221 # "<exec_function>". Don't ever trace these executions, since we
222 # can't do anything with the data later anyway.
223 return False
224
225 if filename.endswith(".html"):
226 # Jinja and maybe other templating systems compile templates into
227 # Python code, but use the template filename as the filename in
228 # the compiled code. Of course, those filenames are useless later
229 # so don't bother collecting. TODO: How should we really separate
230 # out good file extensions from bad?
231 return False
232
233 self._check_for_packages()
234
235 # Compiled Python files have two filenames: frame.f_code.co_filename is
236 # the filename at the time the .pyc was compiled. The second name is
237 # __file__, which is where the .pyc was actually loaded from. Since
238 # .pyc files can be moved after compilation (for example, by being
239 # installed), we look for __file__ in the frame and prefer it to the
240 # co_filename value.
241 dunder_file = frame.f_globals.get('__file__')
242 if dunder_file:
243 filename = self._source_for_file(dunder_file)
244
245 # Jython reports the .class file to the tracer, use the source file.
246 if filename.endswith("$py.class"):
247 filename = filename[:-9] + ".py"
248
249 canonical = self.file_locator.canonical_filename(filename)
250
251 # If the user specified source, then that's authoritative about what to
252 # measure. If they didn't, then we have to exclude the stdlib and
253 # coverage.py directories.
254 if self.source_match:
255 if not self.source_match.match(canonical):
256 return False
257 else:
258 # If we aren't supposed to trace installed code, then check if this
259 # is near the Python standard library and skip it if so.
260 if self.pylib_match and self.pylib_match.match(canonical):
261 return False
262
263 # We exclude the coverage code itself, since a little of it will be
264 # measured otherwise.
265 if self.cover_match and self.cover_match.match(canonical):
266 return False
267
268 # Check the file against the include and omit patterns.
269 if self.include_match and not self.include_match.match(canonical):
270 return False
271 if self.omit_match and self.omit_match.match(canonical):
272 return False
273
274 return canonical
275
276 # To log what should_trace returns, change this to "if 1:"
277 if 0:
278 _real_should_trace = _should_trace
279 def _should_trace(self, filename, frame): # pylint: disable=E0102
280 """A logging decorator around the real _should_trace function."""
281 ret = self._real_should_trace(filename, frame)
282 print("should_trace: %r -> %r" % (filename, ret))
283 return ret
284
285 def _warn(self, msg):
286 """Use `msg` as a warning."""
287 self._warnings.append(msg)
288 sys.stderr.write("Coverage.py warning: %s\n" % msg)
289
290 def _prep_patterns(self, patterns):
291 """Prepare the file patterns for use in a `FnmatchMatcher`.
292
293 If a pattern starts with a wildcard, it is used as a pattern
294 as-is. If it does not start with a wildcard, then it is made
295 absolute with the current directory.
296
297 If `patterns` is None, an empty list is returned.
298
299 """
300 patterns = patterns or []
301 prepped = []
302 for p in patterns or []:
303 if p.startswith("*") or p.startswith("?"):
304 prepped.append(p)
305 else:
306 prepped.append(self.file_locator.abs_file(p))
307 return prepped
308
309 def _check_for_packages(self):
310 """Update the source_match matcher with latest imported packages."""
311 # Our self.source_pkgs attribute is a list of package names we want to
312 # measure. Each time through here, we see if we've imported any of
313 # them yet. If so, we add its file to source_match, and we don't have
314 # to look for that package any more.
315 if self.source_pkgs:
316 found = []
317 for pkg in self.source_pkgs:
318 try:
319 mod = sys.modules[pkg]
320 except KeyError:
321 continue
322
323 found.append(pkg)
324
325 try:
326 pkg_file = mod.__file__
327 except AttributeError:
328 self._warn("Module %s has no Python source." % pkg)
329 else:
330 d, f = os.path.split(pkg_file)
331 if f.startswith('__init__.'):
332 # This is actually a package, return the directory.
333 pkg_file = d
334 else:
335 pkg_file = self._source_for_file(pkg_file)
336 pkg_file = self.file_locator.canonical_filename(pkg_file)
337 self.source.append(pkg_file)
338 self.source_match.add(pkg_file)
339
340 for pkg in found:
341 self.source_pkgs.remove(pkg)
342
343 def use_cache(self, usecache):
344 """Control the use of a data file (incorrectly called a cache).
345
346 `usecache` is true or false, whether to read and write data on disk.
347
348 """
349 self.data.usefile(usecache)
350
351 def load(self):
352 """Load previously-collected coverage data from the data file."""
353 self.collector.reset()
354 self.data.read()
355
356 def start(self):
357 """Start measuring code coverage."""
358 if self.run_suffix:
359 # Calling start() means we're running code, so use the run_suffix
360 # as the data_suffix when we eventually save the data.
361 self.data_suffix = self.run_suffix
362 if self.auto_data:
363 self.load()
364 # Save coverage data when Python exits.
365 if not self.atexit_registered:
366 atexit.register(self.save)
367 self.atexit_registered = True
368
369 # Create the matchers we need for _should_trace
370 if self.source or self.source_pkgs:
371 self.source_match = TreeMatcher(self.source)
372 else:
373 if self.cover_dir:
374 self.cover_match = TreeMatcher([self.cover_dir])
375 if self.pylib_dirs:
376 self.pylib_match = TreeMatcher(self.pylib_dirs)
377 if self.include:
378 self.include_match = FnmatchMatcher(self.include)
379 if self.omit:
380 self.omit_match = FnmatchMatcher(self.omit)
381
382 self._harvested = False
383 self.collector.start()
384
385 def stop(self):
386 """Stop measuring code coverage."""
387 self.collector.stop()
388 self._harvest_data()
389
390 def erase(self):
391 """Erase previously-collected coverage data.
392
393 This removes the in-memory data collected in this session as well as
394 discarding the data file.
395
396 """
397 self.collector.reset()
398 self.data.erase()
399
400 def clear_exclude(self, which='exclude'):
401 """Clear the exclude list."""
402 setattr(self.config, which + "_list", [])
403 self._exclude_regex_stale()
404
405 def exclude(self, regex, which='exclude'):
406 """Exclude source lines from execution consideration.
407
408 A number of lists of regular expressions are maintained. Each list
409 selects lines that are treated differently during reporting.
410
411 `which` determines which list is modified. The "exclude" list selects
412 lines that are not considered executable at all. The "partial" list
413 indicates lines with branches that are not taken.
414
415 `regex` is a regular expression. The regex is added to the specified
416 list. If any of the regexes in the list is found in a line, the line
417 is marked for special treatment during reporting.
418
419 """
420 excl_list = getattr(self.config, which + "_list")
421 excl_list.append(regex)
422 self._exclude_regex_stale()
423
424 def _exclude_regex_stale(self):
425 """Drop all the compiled exclusion regexes, a list was modified."""
426 self._exclude_re.clear()
427
428 def _exclude_regex(self, which):
429 """Return a compiled regex for the given exclusion list."""
430 if which not in self._exclude_re:
431 excl_list = getattr(self.config, which + "_list")
432 self._exclude_re[which] = join_regex(excl_list)
433 return self._exclude_re[which]
434
435 def get_exclude_list(self, which='exclude'):
436 """Return a list of excluded regex patterns.
437
438 `which` indicates which list is desired. See `exclude` for the lists
439 that are available, and their meaning.
440
441 """
442 return getattr(self.config, which + "_list")
443
444 def save(self):
445 """Save the collected coverage data to the data file."""
446 data_suffix = self.data_suffix
447 if data_suffix is True:
448 # If data_suffix was a simple true value, then make a suffix with
449 # plenty of distinguishing information. We do this here in
450 # `save()` at the last minute so that the pid will be correct even
451 # if the process forks.
452 data_suffix = "%s.%s.%06d" % (
453 self.socket.gethostname(), self.os.getpid(),
454 self.random.randint(0, 99999)
455 )
456
457 self._harvest_data()
458 self.data.write(suffix=data_suffix)
459
460 def combine(self):
461 """Combine together a number of similarly-named coverage data files.
462
463 All coverage data files whose name starts with `data_file` (from the
464 coverage() constructor) will be read, and combined together into the
465 current measurements.
466
467 """
468 self.data.combine_parallel_data()
469
470 def _harvest_data(self):
471 """Get the collected data and reset the collector.
472
473 Also warn about various problems collecting data.
474
475 """
476 if not self._harvested:
477 self.data.add_line_data(self.collector.get_line_data())
478 self.data.add_arc_data(self.collector.get_arc_data())
479 self.collector.reset()
480
481 # If there are still entries in the source_pkgs list, then we never
482 # encountered those packages.
483 for pkg in self.source_pkgs:
484 self._warn("Module %s was never imported." % pkg)
485
486 # Find out if we got any data.
487 summary = self.data.summary()
488 if not summary:
489 self._warn("No data was collected.")
490
491 # Find files that were never executed at all.
492 for src in self.source:
493 for py_file in find_python_files(src):
494 self.data.touch_file(py_file)
495
496 self._harvested = True
497
498 # Backward compatibility with version 1.
499 def analysis(self, morf):
500 """Like `analysis2` but doesn't return excluded line numbers."""
501 f, s, _, m, mf = self.analysis2(morf)
502 return f, s, m, mf
503
504 def analysis2(self, morf):
505 """Analyze a module.
506
507 `morf` is a module or a filename. It will be analyzed to determine
508 its coverage statistics. The return value is a 5-tuple:
509
510 * The filename for the module.
511 * A list of line numbers of executable statements.
512 * A list of line numbers of excluded statements.
513 * A list of line numbers of statements not run (missing from
514 execution).
515 * A readable formatted string of the missing line numbers.
516
517 The analysis uses the source file itself and the current measured
518 coverage data.
519
520 """
521 analysis = self._analyze(morf)
522 return (
523 analysis.filename, analysis.statements, analysis.excluded,
524 analysis.missing, analysis.missing_formatted()
525 )
526
527 def _analyze(self, it):
528 """Analyze a single morf or code unit.
529
530 Returns an `Analysis` object.
531
532 """
533 if not isinstance(it, CodeUnit):
534 it = code_unit_factory(it, self.file_locator)[0]
535
536 return Analysis(self, it)
537
538 def report(self, morfs=None, show_missing=True, ignore_errors=None,
539 file=None, # pylint: disable=W0622
540 omit=None, include=None
541 ):
542 """Write a summary report to `file`.
543
544 Each module in `morfs` is listed, with counts of statements, executed
545 statements, missing statements, and a list of lines missed.
546
547 `include` is a list of filename patterns. Modules whose filenames
548 match those patterns will be included in the report. Modules matching
549 `omit` will not be included in the report.
550
551 """
552 self.config.from_args(
553 ignore_errors=ignore_errors, omit=omit, include=include
554 )
555 reporter = SummaryReporter(
556 self, show_missing, self.config.ignore_errors
557 )
558 reporter.report(morfs, outfile=file, config=self.config)
559
560 def annotate(self, morfs=None, directory=None, ignore_errors=None,
561 omit=None, include=None):
562 """Annotate a list of modules.
563
564 Each module in `morfs` is annotated. The source is written to a new
565 file, named with a ",cover" suffix, with each line prefixed with a
566 marker to indicate the coverage of the line. Covered lines have ">",
567 excluded lines have "-", and missing lines have "!".
568
569 See `coverage.report()` for other arguments.
570
571 """
572 self.config.from_args(
573 ignore_errors=ignore_errors, omit=omit, include=include
574 )
575 reporter = AnnotateReporter(self, self.config.ignore_errors)
576 reporter.report(morfs, config=self.config, directory=directory)
577
578 def html_report(self, morfs=None, directory=None, ignore_errors=None,
579 omit=None, include=None):
580 """Generate an HTML report.
581
582 See `coverage.report()` for other arguments.
583
584 """
585 self.config.from_args(
586 ignore_errors=ignore_errors, omit=omit, include=include,
587 html_dir=directory,
588 )
589 reporter = HtmlReporter(self, self.config.ignore_errors)
590 reporter.report(morfs, config=self.config)
591
592 def xml_report(self, morfs=None, outfile=None, ignore_errors=None,
593 omit=None, include=None):
594 """Generate an XML report of coverage results.
595
596 The report is compatible with Cobertura reports.
597
598 Each module in `morfs` is included in the report. `outfile` is the
599 path to write the file to, "-" will write to stdout.
600
601 See `coverage.report()` for other arguments.
602
603 """
604 self.config.from_args(
605 ignore_errors=ignore_errors, omit=omit, include=include,
606 xml_output=outfile,
607 )
608 file_to_close = None
609 if self.config.xml_output:
610 if self.config.xml_output == '-':
611 outfile = sys.stdout
612 else:
613 outfile = open(self.config.xml_output, "w")
614 file_to_close = outfile
615 try:
616 reporter = XmlReporter(self, self.config.ignore_errors)
617 reporter.report(morfs, outfile=outfile, config=self.config)
618 finally:
619 if file_to_close:
620 file_to_close.close()
621
622 def sysinfo(self):
623 """Return a list of (key, value) pairs showing internal information."""
624
625 import coverage as covmod
626 import platform, re
627
628 info = [
629 ('version', covmod.__version__),
630 ('coverage', covmod.__file__),
631 ('cover_dir', self.cover_dir),
632 ('pylib_dirs', self.pylib_dirs),
633 ('tracer', self.collector.tracer_name()),
634 ('data_path', self.data.filename),
635 ('python', sys.version.replace('\n', '')),
636 ('platform', platform.platform()),
637 ('cwd', os.getcwd()),
638 ('path', sys.path),
639 ('environment', [
640 ("%s = %s" % (k, v)) for k, v in os.environ.items()
641 if re.search("^COV|^PY", k)
642 ]),
643 ]
644 return info
645
646
647def process_startup():
648 """Call this at Python startup to perhaps measure coverage.
649
650 If the environment variable COVERAGE_PROCESS_START is defined, coverage
651 measurement is started. The value of the variable is the config file
652 to use.
653
654 There are two ways to configure your Python installation to invoke this
655 function when Python starts:
656
657 #. Create or append to sitecustomize.py to add these lines::
658
659 import coverage
660 coverage.process_startup()
661
662 #. Create a .pth file in your Python installation containing::
663
664 import coverage; coverage.process_startup()
665
666 """
667 cps = os.environ.get("COVERAGE_PROCESS_START")
668 if cps:
669 cov = coverage(config_file=cps, auto_data=True)
670 if os.environ.get("COVERAGE_COVERAGE"):
671 # Measuring coverage within coverage.py takes yet more trickery.
672 cov.cover_dir = "Please measure coverage.py!"
673 cov.start()