diff options
-rw-r--r-- | CHANGES.txt | 5 | ||||
-rw-r--r-- | coverage/config.py | 3 | ||||
-rw-r--r-- | coverage/control.py | 19 | ||||
-rw-r--r-- | coverage/results.py | 34 | ||||
-rw-r--r-- | coverage/summary.py | 5 | ||||
-rw-r--r-- | doc/config.rst | 5 | ||||
-rw-r--r-- | test/test_config.py | 11 | ||||
-rw-r--r-- | test/test_results.py | 14 |
8 files changed, 83 insertions, 13 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 1af4d9ea..88a28b04 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,8 +10,13 @@ Version 3.4b2 percentage is only reported as 0% or 100% if they are truly 0 or 100, and are rounded otherwise. Fixes `issue 41` and issue 70`. +- The precision of reported coverage percentages can be set with the + ``[report] precision`` config file setting. Default is still 0. Completes + ``issue 16``. + .. _issue 70: http://bitbucket.org/ned/coveragepy/issue/70/text-report-and-html-report-disagree-on-coverage .. _issue 41: http://bitbucket.org/ned/coveragepy/issue/41/report-says-100-when-it-isnt-quite-there +.. _issue 16: http://bitbucket.org/ned/coveragepy/issue/16/allow-configuration-of-accuracy-of-percentage-totals Version 3.4b1 --- 21 August 2010 diff --git a/coverage/config.py b/coverage/config.py index 7c22f64b..1f6a879f 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -27,6 +27,7 @@ class CoverageConfig(object): self.ignore_errors = False self.omit = None self.include = None + self.precision = 0 # Defaults for [html] self.html_dir = "htmlcov" @@ -87,6 +88,8 @@ class CoverageConfig(object): self.omit = self.get_list(cp, 'report', 'omit') if cp.has_option('report', 'include'): self.include = self.get_list(cp, 'report', 'include') + if cp.has_option('report', 'precision'): + self.precision = cp.getint('report', 'precision') # [html] if cp.has_option('html', 'directory'): diff --git a/coverage/control.py b/coverage/control.py index 59d237ea..7df608db 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -10,8 +10,8 @@ from coverage.config import CoverageConfig from coverage.data import CoverageData from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher from coverage.html import HtmlReporter -from coverage.misc import bool_or_none -from coverage.results import Analysis +from coverage.misc import CoverageException, bool_or_none +from coverage.results import Analysis, Numbers from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter @@ -76,7 +76,13 @@ class coverage(object): if config_file: if config_file is True: config_file = ".coveragerc" - self.config.from_file(config_file) + try: + self.config.from_file(config_file) + except ValueError: + _, err, _ = sys.exc_info() + raise CoverageException( + "Couldn't read config file %s: %s" % (config_file, err) + ) # 3: from environment variables: self.config.from_environment('COVERAGE_OPTIONS') @@ -163,8 +169,11 @@ class coverage(object): # Only _harvest_data once per measurement cycle. self._harvested = False - # When tearing down the coverage object, modules can become None. - # Saving the modules as object attributes avoids problems, but it is + # 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 diff --git a/coverage/results.py b/coverage/results.py index a3bdec15..e6e39429 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -127,6 +127,12 @@ class Numbers(object): up statistics across files. """ + # A global to determine the precision on coverage percentages, the number + # of decimal places. + _precision = 0 + _near0 = 1.0 # These will change when _precision is changed. + _near100 = 99.0 + def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0, n_branches=0, n_missing_branches=0 ): @@ -137,6 +143,14 @@ class Numbers(object): self.n_branches = n_branches self.n_missing_branches = n_missing_branches + def set_precision(cls, precision): + """Set the number of decimal places used to report percentages.""" + assert 0 <= precision < 10 + cls._precision = precision + cls._near0 = 1.0 / 10**precision + cls._near100 = 100.0 - cls._near0 + set_precision = classmethod(set_precision) + def _get_n_executed(self): """Returns the number of executed statements.""" return self.n_statements - self.n_missing @@ -165,15 +179,23 @@ class Numbers(object): """ pc = self.pc_covered - if 0 < pc < 1: - pc = 1.0 - elif 99 < pc < 100: - pc = 99.0 + if 0 < pc < self._near0: + pc = self._near0 + elif self._near100 < pc < 100: + pc = self._near100 else: - pc = round(pc) - return "%.0f" % pc + pc = round(pc, self._precision) + return "%.*f" % (self._precision, pc) pc_covered_str = property(_get_pc_covered_str) + def pc_str_width(cls): + """How many characters wide can pc_covered_str be?""" + width = 3 # "100" + if cls._precision > 0: + width += 1 + cls._precision + return width + pc_str_width = classmethod(pc_str_width) + def __add__(self, other): nums = Numbers() nums.n_files = self.n_files + other.n_files diff --git a/coverage/summary.py b/coverage/summary.py index 53c844d1..a1206af5 100644 --- a/coverage/summary.py +++ b/coverage/summary.py @@ -31,8 +31,9 @@ class SummaryReporter(Reporter): if self.branches: header += " Branch BrPart" fmt_coverage += " %6d %6d" - header += " Cover" - fmt_coverage += " %5s%%" + width100 = Numbers.pc_str_width() + header += "%*s" % (width100+4, "Cover") + fmt_coverage += "%%%ds%%%%" % (width100+3,) if self.show_missing: header += " Missing" fmt_coverage += " %s" diff --git a/doc/config.rst b/doc/config.rst index b0e5bf84..bcb24412 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -6,6 +6,7 @@ Configuration files :history: 20100223T201600, new for 3.3 :history: 20100725T211700, updated for 3.4. +:history: 20100824T092900, added ``precision``. Coverage.py options can be specified in a configuration file. This makes it @@ -116,6 +117,10 @@ in reporting. See :ref:`source` for details. ``omit`` (multi-string): a list of filename patterns, the files to leave out of reporting. See :ref:`source` for details. +``precision`` (integer): the number of digits after the decimal point to +display for reported coverage percentages. The default is 0, displaying +for example "87%". A value of 2 will display percentages like "87.32%". + [html] ------ diff --git a/test/test_config.py b/test/test_config.py index 96b87650..d5290584 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -2,6 +2,7 @@ import os, sys import coverage +from coverage.misc import CoverageException sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k from coveragetest import CoverageTest @@ -88,6 +89,14 @@ class ConfigTest(CoverageTest): cov = coverage.coverage(data_file="fromarg.dat") self.assertEqual(cov.config.data_file, "fromarg.dat") + def test_parse_errors(self): + # Im-parseable values raise CoverageException + self.make_file(".coveragerc", """\ + [run] + timid = maybe? + """) + self.assertRaises(CoverageException, coverage.coverage) + class ConfigFileTest(CoverageTest): """Tests of the config file settings in particular.""" @@ -116,6 +125,7 @@ class ConfigFileTest(CoverageTest): omit = one, another, some_more, yet_more + precision = 3 [html] @@ -141,6 +151,7 @@ class ConfigFileTest(CoverageTest): self.assertEqual(cov.config.omit, ["one", "another", "some_more", "yet_more"] ) + self.assertEqual(cov.config.precision, 3) self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere") diff --git a/test/test_results.py b/test/test_results.py index 79beb4e9..d6919fa2 100644 --- a/test/test_results.py +++ b/test/test_results.py @@ -49,3 +49,17 @@ class NumbersTest(CoverageTest): self.assertEqual(n1.pc_covered_str, "99") self.assertEqual(n999.pc_covered_str, "1") self.assertEqual(n1000.pc_covered_str, "0") + + def test_pc_covered_str_precision(self): + assert Numbers._precision == 0 + Numbers.set_precision(1) + n0 = Numbers(n_files=1, n_statements=10000, n_missing=0) + n1 = Numbers(n_files=1, n_statements=10000, n_missing=1) + n9999 = Numbers(n_files=1, n_statements=10000, n_missing=9999) + n10000 = Numbers(n_files=1, n_statements=10000, n_missing=10000) + self.assertEqual(n0.pc_covered_str, "100.0") + self.assertEqual(n1.pc_covered_str, "99.9") + self.assertEqual(n9999.pc_covered_str, "0.1") + self.assertEqual(n10000.pc_covered_str, "0.0") + Numbers.set_precision(0) + |