1 """Core control stuff for Coverage."""
3 import atexit
, os
, random
, socket
, sys
5 from coverage
.annotate
import AnnotateReporter
6 from coverage
.backward
import string_class
, iitems
, sorted # pylint: disable=W0622
7 from coverage
.codeunit
import code_unit_factory
, CodeUnit
8 from coverage
.collector
import Collector
9 from coverage
.config
import CoverageConfig
10 from coverage
.data
import CoverageData
11 from coverage
.debug
import DebugControl
12 from coverage
.files
import FileLocator
, TreeMatcher
, FnmatchMatcher
13 from coverage
.files
import PathAliases
, find_python_files
, prep_patterns
14 from coverage
.html
import HtmlReporter
15 from coverage
.misc
import CoverageException
, bool_or_none
, join_regex
16 from coverage
.misc
import file_be_gone
17 from coverage
.results
import Analysis
, Numbers
18 from coverage
.summary
import SummaryReporter
19 from coverage
.xmlreport
import XmlReporter
21 # Pypy has some unusual stuff in the "stdlib". Consider those locations
22 # when deciding where the stdlib is.
24 import _structseq
# pylint: disable=F0401
29 class coverage(object):
30 """Programmatic access to coverage.py.
34 from coverage import coverage
40 cov.html_report(directory='covhtml')
43 def __init__(self
, data_file
=None, data_suffix
=None, cover_pylib
=None,
44 auto_data
=False, timid
=None, branch
=None, config_file
=True,
45 source
=None, omit
=None, include
=None, debug
=None,
48 `data_file` is the base name of the data file to use, defaulting to
49 ".coverage". `data_suffix` is appended (with a dot) to `data_file` to
50 create the final file name. If `data_suffix` is simply True, then a
51 suffix is created with the machine and process identity included.
53 `cover_pylib` is a boolean determining whether Python code installed
54 with the Python interpreter is measured. This includes the Python
55 standard library and any packages installed with the interpreter.
57 If `auto_data` is true, then any existing data file will be read when
58 coverage measurement starts, and data will be saved automatically when
61 If `timid` is true, then a slower and simpler trace function will be
62 used. This is important for some environments where manipulation of
63 tracing functions breaks the faster trace function.
65 If `branch` is true, then branch coverage will be measured in addition
66 to the usual statement coverage.
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
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
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.
81 `debug` is a list of strings indicating what debugging information is
82 desired. `debug_file` is the file to write debug messages to,
86 from coverage
import __version__
88 # A record of all the warnings that have been issued.
91 # Build our configuration from a number of sources:
93 self
.config
= CoverageConfig()
95 # 2: from the coveragerc file:
97 if config_file
is True:
98 config_file
= ".coveragerc"
100 self
.config
.from_file(config_file
)
102 _
, err
, _
= sys
.exc_info()
103 raise CoverageException(
104 "Couldn't read config file %s: %s" % (config_file
, err
)
107 # 3: from environment variables:
108 self
.config
.from_environment('COVERAGE_OPTIONS')
109 env_data_file
= os
.environ
.get('COVERAGE_FILE')
111 self
.config
.data_file
= env_data_file
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
,
120 # Create and configure the debugging controller.
121 self
.debug
= DebugControl(self
.config
.debug
, debug_file
or sys
.stderr
)
123 self
.auto_data
= auto_data
125 # _exclude_re is a dict mapping exclusion list names to compiled
127 self
._exclude
_re
= {}
128 self
._exclude
_regex
_stale
()
130 self
.file_locator
= FileLocator()
132 # The source argument can be directories or package names.
134 self
.source_pkgs
= []
135 for src
in self
.config
.source
or []:
136 if os
.path
.exists(src
):
137 self
.source
.append(self
.file_locator
.canonical_filename(src
))
139 self
.source_pkgs
.append(src
)
141 self
.omit
= prep_patterns(self
.config
.omit
)
142 self
.include
= prep_patterns(self
.config
.include
)
144 self
.collector
= Collector(
145 self
._should
_trace
, timid
=self
.config
.timid
,
146 branch
=self
.config
.branch
, warn
=self
._warn
149 # Suffixes are a bit tricky. We want to use the data suffix only when
150 # collecting data, not when combining data. So we save it as
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
:
154 if not isinstance(data_suffix
, string_class
):
155 # if data_suffix=True, use .machinename.pid.random
159 self
.data_suffix
= None
160 self
.run_suffix
= data_suffix
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.
165 self
.data
= CoverageData(
166 basename
=self
.config
.data_file
,
167 collector
="coverage v%s" % __version__
,
171 # The dirs for files considered "installed with the interpreter".
173 if not self
.config
.cover_pylib
:
174 # Look at where some standard modules are located. That's the
175 # indication for "installed with the interpreter". In some
176 # environments (virtualenv, for example), these modules may be
177 # spread across a few locations. Look at all the candidate modules
178 # we've imported, and take all the different ones.
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
)
185 # To avoid tracing the coverage code itself, we skip anything located
187 self
.cover_dir
= self
._canonical
_dir
(__file__
)
189 # The matchers for _should_trace.
190 self
.source_match
= None
191 self
.pylib_match
= self
.cover_match
= None
192 self
.include_match
= self
.omit_match
= None
194 # Set the reporting precision.
195 Numbers
.set_precision(self
.config
.precision
)
197 # Is it ok for no data to be collected?
198 self
._warn
_no
_data
= True
199 self
._warn
_unimported
_source
= True
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
207 atexit
.register(self
._atexit
)
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]
213 def _source_for_file(self
, filename
):
214 """Return the source file for `filename`."""
215 if not filename
.endswith(".py"):
216 if filename
[-4:-1] == ".py":
217 filename
= filename
[:-1]
218 elif filename
.endswith("$py.class"): # jython
219 filename
= filename
[:-9] + ".py"
222 def _should_trace_with_reason(self
, filename
, frame
):
223 """Decide whether to trace execution in `filename`, with a reason.
225 This function is called from the trace function. As each new file name
226 is encountered, this function determines whether it is traced or not.
228 Returns a pair of values: the first indicates whether the file should
229 be traced: it's a canonicalized filename if it should be traced, None
230 if it should not. The second value is a string, the resason for the
235 # Empty string is pretty useless
236 return None, "empty string isn't a filename"
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"
245 self
._check
_for
_packages
()
247 # Compiled Python files have two filenames: frame.f_code.co_filename is
248 # the filename at the time the .pyc was compiled. The second name is
249 # __file__, which is where the .pyc was actually loaded from. Since
250 # .pyc files can be moved after compilation (for example, by being
251 # installed), we look for __file__ in the frame and prefer it to the
253 dunder_file
= frame
.f_globals
.get('__file__')
255 filename
= self
._source
_for
_file
(dunder_file
)
257 # Jython reports the .class file to the tracer, use the source file.
258 if filename
.endswith("$py.class"):
259 filename
= filename
[:-9] + ".py"
261 canonical
= self
.file_locator
.canonical_filename(filename
)
263 # If the user specified source or include, then that's authoritative
264 # about the outer bound of what to measure and we don't have to apply
265 # any canned exclusions. If they didn't, then we have to exclude the
266 # stdlib and coverage.py directories.
267 if self
.source_match
:
268 if not self
.source_match
.match(canonical
):
269 return None, "falls outside the --source trees"
270 elif self
.include_match
:
271 if not self
.include_match
.match(canonical
):
272 return None, "falls outside the --include trees"
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"
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"
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"
288 return canonical
, "because we love you"
290 def _should_trace(self
, filename
, frame
):
291 """Decide whether to trace execution in `filename`.
293 Calls `_should_trace_with_reason`, and returns just the decision.
296 canonical
, reason
= self
._should
_trace
_with
_reason
(filename
, frame
)
297 if self
.debug
.should('trace'):
299 msg
= "Not tracing %r: %s" % (filename
, reason
)
301 msg
= "Tracing %r" % (filename
,)
302 self
.debug
.write(msg
)
305 def _warn(self
, msg
):
306 """Use `msg` as a warning."""
307 self
._warnings
.append(msg
)
308 sys
.stderr
.write("Coverage.py warning: %s\n" % msg
)
310 def _check_for_packages(self
):
311 """Update the source_match matcher with latest imported packages."""
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.
318 for pkg
in self
.source_pkgs
:
320 mod
= sys
.modules
[pkg
]
327 pkg_file
= mod
.__file
__
328 except AttributeError:
331 d
, f
= os
.path
.split(pkg_file
)
332 if f
.startswith('__init__'):
333 # This is actually a package, return the directory.
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
):
342 self
.source
.append(pkg_file
)
343 self
.source_match
.add(pkg_file
)
345 self
._warn
("Module %s has no Python source." % pkg
)
348 self
.source_pkgs
.remove(pkg
)
350 def use_cache(self
, usecache
):
351 """Control the use of a data file (incorrectly called a cache).
353 `usecache` is true or false, whether to read and write data on disk.
356 self
.data
.usefile(usecache
)
359 """Load previously-collected coverage data from the data file."""
360 self
.collector
.reset()
364 """Start measuring code coverage.
366 Coverage measurement actually occurs in functions called after `start`
367 is invoked. Statements in the same scope as `start` won't be measured.
369 Once you invoke `start`, you must also call `stop` eventually, or your
370 process might not shut down cleanly.
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
380 # Create the matchers we need for _should_trace
381 if self
.source
or self
.source_pkgs
:
382 self
.source_match
= TreeMatcher(self
.source
)
385 self
.cover_match
= TreeMatcher([self
.cover_dir
])
387 self
.pylib_match
= TreeMatcher(self
.pylib_dirs
)
389 self
.include_match
= FnmatchMatcher(self
.include
)
391 self
.omit_match
= FnmatchMatcher(self
.omit
)
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
)
399 if self
.debug
.should('sys'):
400 self
.debug
.write("Debugging info:")
401 self
.debug
.write_formatted_info(self
.sysinfo())
403 self
.collector
.start()
405 self
._measured
= True
408 """Stop measuring code coverage."""
409 self
._started
= False
410 self
.collector
.stop()
413 """Clean up on process shutdown."""
420 """Erase previously-collected coverage data.
422 This removes the in-memory data collected in this session as well as
423 discarding the data file.
426 self
.collector
.reset()
429 def clear_exclude(self
, which
='exclude'):
430 """Clear the exclude list."""
431 setattr(self
.config
, which
+ "_list", [])
432 self
._exclude
_regex
_stale
()
434 def exclude(self
, regex
, which
='exclude'):
435 """Exclude source lines from execution consideration.
437 A number of lists of regular expressions are maintained. Each list
438 selects lines that are treated differently during reporting.
440 `which` determines which list is modified. The "exclude" list selects
441 lines that are not considered executable at all. The "partial" list
442 indicates lines with branches that are not taken.
444 `regex` is a regular expression. The regex is added to the specified
445 list. If any of the regexes in the list is found in a line, the line
446 is marked for special treatment during reporting.
449 excl_list
= getattr(self
.config
, which
+ "_list")
450 excl_list
.append(regex
)
451 self
._exclude
_regex
_stale
()
453 def _exclude_regex_stale(self
):
454 """Drop all the compiled exclusion regexes, a list was modified."""
455 self
._exclude
_re
.clear()
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
]
464 def get_exclude_list(self
, which
='exclude'):
465 """Return a list of excluded regex patterns.
467 `which` indicates which list is desired. See `exclude` for the lists
468 that are available, and their meaning.
471 return getattr(self
.config
, which
+ "_list")
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.
483 f
= open(_TEST_NAME_FILE
)
486 extra
= "." + test_name
487 data_suffix
= "%s%s.%s.%06d" % (
488 socket
.gethostname(), extra
, os
.getpid(),
489 random
.randint(0, 999999)
493 self
.data
.write(suffix
=data_suffix
)
496 """Combine together a number of similarly-named coverage data files.
498 All coverage data files whose name starts with `data_file` (from the
499 coverage() constructor) will be read, and combined together into the
500 current measurements.
504 if self
.config
.paths
:
505 aliases
= PathAliases(self
.file_locator
)
506 for paths
in self
.config
.paths
.values():
508 for pattern
in paths
[1:]:
509 aliases
.add(pattern
, result
)
510 self
.data
.combine_parallel_data(aliases
=aliases
)
512 def _harvest_data(self
):
513 """Get the collected data and reset the collector.
515 Also warn about various problems collecting data.
518 if not self
._measured
:
521 self
.data
.add_line_data(self
.collector
.get_line_data())
522 self
.data
.add_arc_data(self
.collector
.get_arc_data())
523 self
.collector
.reset()
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
)
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.")
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
)
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
546 self
.data
.touch_file(py_file
)
548 self
._measured
= False
550 # Backward compatibility with version 1.
551 def analysis(self
, morf
):
552 """Like `analysis2` but doesn't return excluded line numbers."""
553 f
, s
, _
, m
, mf
= self
.analysis2(morf
)
556 def analysis2(self
, morf
):
559 `morf` is a module or a filename. It will be analyzed to determine
560 its coverage statistics. The return value is a 5-tuple:
562 * The filename for the module.
563 * A list of line numbers of executable statements.
564 * A list of line numbers of excluded statements.
565 * A list of line numbers of statements not run (missing from
567 * A readable formatted string of the missing line numbers.
569 The analysis uses the source file itself and the current measured
573 analysis
= self
._analyze
(morf
)
576 sorted(analysis
.statements
),
577 sorted(analysis
.excluded
),
578 sorted(analysis
.missing
),
579 analysis
.missing_formatted(),
582 def _analyze(self
, it
):
583 """Analyze a single morf or code unit.
585 Returns an `Analysis` object.
589 if not isinstance(it
, CodeUnit
):
590 it
= code_unit_factory(it
, self
.file_locator
)[0]
592 return Analysis(self
, it
)
594 def report(self
, morfs
=None, show_missing
=True, ignore_errors
=None,
595 file=None, # pylint: disable=W0622
596 omit
=None, include
=None
598 """Write a summary report to `file`.
600 Each module in `morfs` is listed, with counts of statements, executed
601 statements, missing statements, and a list of lines missed.
603 `include` is a list of filename patterns. Modules whose filenames
604 match those patterns will be included in the report. Modules matching
605 `omit` will not be included in the report.
607 Returns a float, the total percentage covered.
611 self
.config
.from_args(
612 ignore_errors
=ignore_errors
, omit
=omit
, include
=include
,
613 show_missing
=show_missing
,
615 reporter
= SummaryReporter(self
, self
.config
)
616 return reporter
.report(morfs
, outfile
=file)
618 def annotate(self
, morfs
=None, directory
=None, ignore_errors
=None,
619 omit
=None, include
=None):
620 """Annotate a list of modules.
622 Each module in `morfs` is annotated. The source is written to a new
623 file, named with a ",cover" suffix, with each line prefixed with a
624 marker to indicate the coverage of the line. Covered lines have ">",
625 excluded lines have "-", and missing lines have "!".
627 See `coverage.report()` for other arguments.
631 self
.config
.from_args(
632 ignore_errors
=ignore_errors
, omit
=omit
, include
=include
634 reporter
= AnnotateReporter(self
, self
.config
)
635 reporter
.report(morfs
, directory
=directory
)
637 def html_report(self
, morfs
=None, directory
=None, ignore_errors
=None,
638 omit
=None, include
=None, extra_css
=None, title
=None):
639 """Generate an HTML report.
641 The HTML is written to `directory`. The file "index.html" is the
642 overview starting point, with links to more detailed pages for
645 `extra_css` is a path to a file of other CSS to apply on the page.
646 It will be copied into the HTML directory.
648 `title` is a text string (not HTML) to use as the title of the HTML
651 See `coverage.report()` for other arguments.
653 Returns a float, the total percentage covered.
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
,
661 reporter
= HtmlReporter(self
, self
.config
)
662 return reporter
.report(morfs
)
664 def xml_report(self
, morfs
=None, outfile
=None, ignore_errors
=None,
665 omit
=None, include
=None):
666 """Generate an XML report of coverage results.
668 The report is compatible with Cobertura reports.
670 Each module in `morfs` is included in the report. `outfile` is the
671 path to write the file to, "-" will write to stdout.
673 See `coverage.report()` for other arguments.
675 Returns a float, the total percentage covered.
679 self
.config
.from_args(
680 ignore_errors
=ignore_errors
, omit
=omit
, include
=include
,
685 if self
.config
.xml_output
:
686 if self
.config
.xml_output
== '-':
689 outfile
= open(self
.config
.xml_output
, "w")
690 file_to_close
= outfile
693 reporter
= XmlReporter(self
, self
.config
)
694 return reporter
.report(morfs
, outfile
=outfile
)
695 except CoverageException
:
700 file_to_close
.close()
702 file_be_gone(self
.config
.xml_output
)
705 """Return a list of (key, value) pairs showing internal information."""
707 import coverage
as covmod
711 implementation
= platform
.python_implementation()
712 except AttributeError:
713 implementation
= "unknown"
716 ('version', covmod
.__version
__),
717 ('coverage', covmod
.__file
__),
718 ('cover_dir', self
.cover_dir
),
719 ('pylib_dirs', self
.pylib_dirs
),
720 ('tracer', self
.collector
.tracer_name()),
721 ('config_files', self
.config
.attempted_config_files
),
722 ('configs_read', self
.config
.config_files
),
723 ('data_path', self
.data
.filename
),
724 ('python', sys
.version
.replace('\n', '')),
725 ('platform', platform
.platform()),
726 ('implementation', implementation
),
727 ('executable', sys
.executable
),
728 ('cwd', os
.getcwd()),
730 ('environment', sorted([
731 ("%s = %s" % (k
, v
)) for k
, v
in iitems(os
.environ
)
732 if re
.search(r
"^COV|^PY", k
)
734 ('command_line', " ".join(getattr(sys
, 'argv', ['???']))),
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()))
741 info
.append(('omit_match', self
.omit_match
.info()))
743 info
.append(('cover_match', self
.cover_match
.info()))
745 info
.append(('pylib_match', self
.pylib_match
.info()))
750 def process_startup():
751 """Call this at Python startup to perhaps measure coverage.
753 If the environment variable COVERAGE_PROCESS_START is defined, coverage
754 measurement is started. The value of the variable is the config file
757 There are two ways to configure your Python installation to invoke this
758 function when Python starts:
760 #. Create or append to sitecustomize.py to add these lines::
763 coverage.process_startup()
765 #. Create a .pth file in your Python installation containing::
767 import coverage; coverage.process_startup()
770 cps
= os
.environ
.get("COVERAGE_PROCESS_START")
772 cov
= coverage(config_file
=cps
, auto_data
=True)
774 cov
._warn
_no
_data
= False
775 cov
._warn
_unimported
_source
= False
778 # A hack for debugging testing in subprocesses.
779 _TEST_NAME_FILE
= "" #"/tmp/covtest.txt"