diff options
Diffstat (limited to 'coverage/control.py')
-rw-r--r-- | coverage/control.py | 68 |
1 files changed, 43 insertions, 25 deletions
diff --git a/coverage/control.py b/coverage/control.py index acca99ee..f80e62b6 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -3,7 +3,7 @@ import atexit, os, random, socket, sys from coverage.annotate import AnnotateReporter -from coverage.backward import string_class +from coverage.backward import string_class, iitems from coverage.codeunit import code_unit_factory, CodeUnit from coverage.collector import Collector from coverage.config import CoverageConfig @@ -12,12 +12,13 @@ from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher from coverage.files import PathAliases, find_python_files, prep_patterns from coverage.html import HtmlReporter from coverage.misc import CoverageException, bool_or_none, join_regex +from coverage.misc import file_be_gone from coverage.results import Analysis, Numbers from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter class coverage(object): - """Programmatic access to Coverage. + """Programmatic access to coverage.py. To use:: @@ -25,7 +26,7 @@ class coverage(object): cov = coverage() cov.start() - #.. blah blah (run your code) blah blah .. + #.. call your code .. cov.stop() cov.html_report(directory='covhtml') @@ -179,14 +180,6 @@ class coverage(object): # Set the reporting precision. Numbers.set_precision(self.config.precision) - # When tearing down the coverage object, modules can become None. - # Saving the modules as object attributes avoids problems, but it is - # quite ad-hoc which modules need to be saved and which references - # need to use the object attributes. - self.socket = socket - self.os = os - self.random = random - def _canonical_dir(self, f): """Return the canonical directory of the file `f`.""" return os.path.split(self.file_locator.canonical_filename(f))[0] @@ -208,9 +201,6 @@ class coverage(object): should not. """ - if os is None: - return False - if filename.startswith('<'): # Lots of non-file execution is represented with artificial # filenames like "<string>", "<doctest readme.txt[0]>", or @@ -331,7 +321,15 @@ class coverage(object): self.data.read() def start(self): - """Start measuring code coverage.""" + """Start measuring code coverage. + + Coverage measurement actually occurs in functions called after `start` + is invoked. Statements in the same scope as `start` won't be measured. + + Once you invoke `start`, you must also call `stop` eventually, or your + process might not shut down cleanly. + + """ if self.run_suffix: # Calling start() means we're running code, so use the run_suffix # as the data_suffix when we eventually save the data. @@ -362,7 +360,6 @@ class coverage(object): def stop(self): """Stop measuring code coverage.""" self.collector.stop() - self._harvest_data() def erase(self): """Erase previously-collected coverage data. @@ -427,8 +424,8 @@ class coverage(object): # `save()` at the last minute so that the pid will be correct even # if the process forks. data_suffix = "%s.%s.%06d" % ( - self.socket.gethostname(), self.os.getpid(), - self.random.randint(0, 99999) + socket.gethostname(), os.getpid(), + random.randint(0, 99999) ) self._harvest_data() @@ -475,6 +472,7 @@ class coverage(object): # Find files that were never executed at all. for src in self.source: for py_file in find_python_files(src): + py_file = self.file_locator.canonical_filename(py_file) self.data.touch_file(py_file) self._harvested = True @@ -514,6 +512,7 @@ class coverage(object): Returns an `Analysis` object. """ + self._harvest_data() if not isinstance(it, CodeUnit): it = code_unit_factory(it, self.file_locator)[0] @@ -532,13 +531,16 @@ class coverage(object): match those patterns will be included in the report. Modules matching `omit` will not be included in the report. + Returns a float, the total percentage covered. + """ + self._harvest_data() self.config.from_args( ignore_errors=ignore_errors, omit=omit, include=include, show_missing=show_missing, ) reporter = SummaryReporter(self, self.config) - reporter.report(morfs, outfile=file) + return reporter.report(morfs, outfile=file) def annotate(self, morfs=None, directory=None, ignore_errors=None, omit=None, include=None): @@ -552,6 +554,7 @@ class coverage(object): See `coverage.report()` for other arguments. """ + self._harvest_data() self.config.from_args( ignore_errors=ignore_errors, omit=omit, include=include ) @@ -559,7 +562,7 @@ class coverage(object): reporter.report(morfs, directory=directory) def html_report(self, morfs=None, directory=None, ignore_errors=None, - omit=None, include=None, extra_css=None): + omit=None, include=None, extra_css=None, title=None): """Generate an HTML report. The HTML is written to `directory`. The file "index.html" is the @@ -569,15 +572,21 @@ class coverage(object): `extra_css` is a path to a file of other CSS to apply on the page. It will be copied into the HTML directory. + `title` is a text string (not HTML) to use as the title of the HTML + report. + See `coverage.report()` for other arguments. + Returns a float, the total percentage covered. + """ + self._harvest_data() self.config.from_args( ignore_errors=ignore_errors, omit=omit, include=include, - html_dir=directory, extra_css=extra_css, + html_dir=directory, extra_css=extra_css, html_title=title, ) reporter = HtmlReporter(self, self.config) - reporter.report(morfs) + return reporter.report(morfs) def xml_report(self, morfs=None, outfile=None, ignore_errors=None, omit=None, include=None): @@ -590,12 +599,16 @@ class coverage(object): See `coverage.report()` for other arguments. + Returns a float, the total percentage covered. + """ + self._harvest_data() self.config.from_args( ignore_errors=ignore_errors, omit=omit, include=include, xml_output=outfile, ) file_to_close = None + delete_file = False if self.config.xml_output: if self.config.xml_output == '-': outfile = sys.stdout @@ -604,10 +617,15 @@ class coverage(object): file_to_close = outfile try: reporter = XmlReporter(self, self.config) - reporter.report(morfs, outfile=outfile) + return reporter.report(morfs, outfile=outfile) + except CoverageException: + delete_file = True + raise finally: if file_to_close: file_to_close.close() + if delete_file: + file_be_gone(self.config.xml_output) def sysinfo(self): """Return a list of (key, value) pairs showing internal information.""" @@ -633,8 +651,8 @@ class coverage(object): ('cwd', os.getcwd()), ('path', sys.path), ('environment', [ - ("%s = %s" % (k, v)) for k, v in os.environ.items() - if re.search("^COV|^PY", k) + ("%s = %s" % (k, v)) for k, v in iitems(os.environ) + if re.search(r"^COV|^PY", k) ]), ] return info |