summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst6
-rw-r--r--CONTRIBUTORS.txt1
-rw-r--r--coverage/cmdline.py5
-rw-r--r--coverage/config.py4
-rw-r--r--coverage/results.py24
-rw-r--r--doc/config.rst10
-rw-r--r--tests/test_cmdline.py12
-rw-r--r--tests/test_process.py22
-rw-r--r--tests/test_results.py35
9 files changed, 72 insertions, 47 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index c4ec5bf4..8ccc8008 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -13,6 +13,11 @@ Unreleased
See :ref:`api_plugin` for details. This solves the complex configuration
problem described in `issue 563`_.
+- The ``fail_under`` option can now be a float. Note that you must specify the
+ ``[report] precision`` configuration option for the fractional part to be
+ used. Thanks to Lars Hupfeldt Nielsen for help with the implementation.
+ Fixes `issue 631`_.
+
- The ``include`` and ``omit`` options can be specified for both the ``[run]``
and ``[report]`` phases of execution. 4.4.2 introduced some incorrect
interations between those phases, where the options for one were confused for
@@ -28,6 +33,7 @@ Unreleased
.. _issue 621: https://bitbucket.org/ned/coveragepy/issues/621/include-ignored-warning-when-using
.. _issue 622: https://bitbucket.org/ned/coveragepy/issues/622/report-omit-overwrites-run-omit
.. _issue 627: https://bitbucket.org/ned/coveragepy/issues/627/failure-generating-html-reports-when-the
+.. _issue 631: https://bitbucket.org/ned/coveragepy/issues/631/precise-coverage-percentage-value
.. _changes_442:
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 953512a6..52cedc0f 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -65,6 +65,7 @@ Josh Williams
Julian Berman
Krystian Kichewko
Kyle Altendorf
+Lars Hupfeldt Nielsen
Leonardo Pistone
Lex Berezhny
Loïc Dachary
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 841d8f4a..7b86054e 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -51,7 +51,7 @@ class Opts(object):
help="Write the output files to DIR.",
)
fail_under = optparse.make_option(
- '', '--fail-under', action='store', metavar="MIN", type="int",
+ '', '--fail-under', action='store', metavar="MIN", type="float",
help="Exit with a status of 2 if the total coverage is less than MIN.",
)
help = optparse.make_option(
@@ -528,7 +528,8 @@ class CoverageScript(object):
self.coverage.set_option("report:fail_under", options.fail_under)
fail_under = self.coverage.get_option("report:fail_under")
- if should_fail_under(total, fail_under):
+ precision = self.coverage.get_option("report:precision")
+ if should_fail_under(total, fail_under, precision):
return FAIL_UNDER
return OK
diff --git a/coverage/config.py b/coverage/config.py
index 500c2119..7b8f2bd0 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -199,7 +199,7 @@ class CoverageConfig(object):
# Defaults for [report]
self.exclude_list = DEFAULT_EXCLUDE[:]
- self.fail_under = 0
+ self.fail_under = 0.0
self.ignore_errors = False
self.report_include = None
self.report_omit = None
@@ -335,7 +335,7 @@ class CoverageConfig(object):
# [report]
('exclude_list', 'report:exclude_lines', 'regexlist'),
- ('fail_under', 'report:fail_under', 'int'),
+ ('fail_under', 'report:fail_under', 'float'),
('ignore_errors', 'report:ignore_errors', 'boolean'),
('partial_always_list', 'report:partial_branches_always', 'regexlist'),
('partial_list', 'report:partial_branches', 'regexlist'),
diff --git a/coverage/results.py b/coverage/results.py
index 81ce2a68..5f84a689 100644
--- a/coverage/results.py
+++ b/coverage/results.py
@@ -6,7 +6,7 @@
import collections
from coverage.backward import iitems
-from coverage.misc import format_lines, SimpleRepr
+from coverage.misc import contract, format_lines, SimpleRepr
class Analysis(object):
@@ -271,25 +271,19 @@ class Numbers(SimpleRepr):
return NotImplemented
-def should_fail_under(total, fail_under):
+@contract(total='number', fail_under='number', precision=int, returns=bool)
+def should_fail_under(total, fail_under, precision):
"""Determine if a total should fail due to fail-under.
`total` is a float, the coverage measurement total. `fail_under` is the
- fail_under setting to compare with.
+ fail_under setting to compare with. `precision` is the number of digits
+ to consider after the decimal point.
Returns True if the total should fail.
"""
- # The fail_under option defaults to 0.
- if fail_under:
- # Total needs to be rounded, but don't want to report 100
- # unless it is really 100.
- if 99 < total < 100:
- total = 99
- else:
- total = round(total)
-
- if total < fail_under:
- return True
+ # Special case for fail_under=100, it must really be 100.
+ if fail_under == 100.0 and total != 100.0:
+ return True
- return False
+ return round(total, precision) < fail_under
diff --git a/doc/config.rst b/doc/config.rst
index e6275499..c1fb4b1b 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -202,8 +202,11 @@ reported as missing. More details are in :ref:`excluding`. If you use this
option, you are replacing all the exclude regexes, so you'll need to also
supply the "pragma: no cover" regex if you still want to use it.
-``fail_under`` (integer): a target coverage percentage. If the total coverage
-measurement is under this value, then exit with a status code of 2.
+``fail_under`` (float): a target coverage percentage. If the total coverage
+measurement is under this value, then exit with a status code of 2. If you
+specify a non-integral value, you must also set ``[report] precision`` properly
+to make use of the decimal places. A setting of 100 will fail any value under
+100, regardless of the number of decimal places of precision.
``ignore_errors`` (boolean, default False): ignore source code that can't be
found, emitting a warning instead of an exception.
@@ -222,7 +225,8 @@ supply the "pragma: no branch" regex if you still want to use it.
``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%".
+example "87%". A value of 2 will display percentages like "87.32%". This
+setting also affects the interpretation of the ``fail_under`` setting.
``show_missing`` (boolean, default False): when running a summary report, show
missing lines. See :ref:`cmd_summary` for more information.
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 9d64d864..1b7c6653 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -63,6 +63,11 @@ class BaseCmdLineTest(CoverageTest):
config = CoverageConfig()
mk.get_option = config.get_option
+ # Get the type right for the result of reporting.
+ mk.report.return_value = 50.0
+ mk.html_report.return_value = 50.0
+ mk.xml_report.return_value = 50.0
+
return mk
def mock_command_line(self, args, path_exists=None):
@@ -755,6 +760,7 @@ class CoverageReportingFake(object):
"""A fake Coverage and Coverage.coverage test double."""
# pylint: disable=missing-docstring
def __init__(self, report_result, html_result, xml_result):
+ self.config = CoverageConfig()
self.report_result = report_result
self.html_result = html_result
self.xml_result = xml_result
@@ -763,10 +769,10 @@ class CoverageReportingFake(object):
return self
def set_option(self, optname, optvalue):
- setattr(self, optname, optvalue)
+ self.config.set_option(optname, optvalue)
def get_option(self, optname):
- return getattr(self, optname)
+ return self.config.get_option(optname)
def load(self):
pass
@@ -801,7 +807,7 @@ class CoverageReportingFake(object):
])
def test_fail_under(results, fail_under, cmd, ret):
cov = CoverageReportingFake(*results)
- if fail_under:
+ if fail_under is not None:
cov.set_option("report:fail_under", fail_under)
ret_actual = command_line(cmd, _covpkg=cov)
assert ret_actual == ret
diff --git a/tests/test_process.py b/tests/test_process.py
index 2d4e72f0..42a6d983 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -960,18 +960,22 @@ class FailUnderTest(CoverageTest):
""")
st, _ = self.run_command_status("coverage run forty_two_plus.py")
self.assertEqual(st, 0)
- st, out = self.run_command_status("coverage report")
- self.assertEqual(st, 0)
- self.assertEqual(
- self.last_line_squeezed(out),
- "forty_two_plus.py 7 4 43%"
- )
- def test_report(self):
- st, _ = self.run_command_status("coverage report --fail-under=43")
+ def test_report_43_is_ok(self):
+ st, out = self.run_command_status("coverage report --fail-under=43")
self.assertEqual(st, 0)
- st, _ = self.run_command_status("coverage report --fail-under=44")
+ self.assertEqual(self.last_line_squeezed(out), "forty_two_plus.py 7 4 43%")
+
+ def test_report_43_is_not_ok(self):
+ st, out = self.run_command_status("coverage report --fail-under=44")
+ self.assertEqual(st, 2)
+ self.assertEqual(self.last_line_squeezed(out), "forty_two_plus.py 7 4 43%")
+
+ def test_report_42p86_is_not_ok(self):
+ self.make_file(".coveragerc", "[report]\nprecision = 2")
+ st, out = self.run_command_status("coverage report --fail-under=42.88")
self.assertEqual(st, 2)
+ self.assertEqual(self.last_line_squeezed(out), "forty_two_plus.py 7 4 42.86%")
class FailUnderNoFilesTest(CoverageTest):
diff --git a/tests/test_results.py b/tests/test_results.py
index 280694d0..deaf8113 100644
--- a/tests/test_results.py
+++ b/tests/test_results.py
@@ -78,21 +78,30 @@ class NumbersTest(CoverageTest):
self.assertEqual(n.ratio_covered, (160, 210))
-@pytest.mark.parametrize("total, fail_under, result", [
+@pytest.mark.parametrize("total, fail_under, precision, result", [
# fail_under==0 means anything is fine!
- (0, 0, False),
- (0.001, 0, False),
+ (0, 0, 0, False),
+ (0.001, 0, 0, False),
# very small fail_under is possible to fail.
- (0.001, 0.01, True),
+ (0.001, 0.01, 0, True),
# Rounding should work properly.
- (42.1, 42, False),
- (42.1, 43, True),
- (42.857, 42, False),
- (42.857, 43, False),
- (42.857, 44, True),
+ (42.1, 42, 0, False),
+ (42.1, 43, 0, True),
+ (42.857, 42, 0, False),
+ (42.857, 43, 0, False),
+ (42.857, 44, 0, True),
+ (42.857, 42.856, 3, False),
+ (42.857, 42.858, 3, True),
+ # If you don't specify precision, your fail-under is rounded.
+ (42.857, 42.856, 0, False),
# Values near 100 should only be treated as 100 if they are 100.
- (99.8, 100, True),
- (100.0, 100, False),
+ (99.8, 100, 0, True),
+ (100.0, 100, 0, False),
+ (99.8, 99.7, 1, False),
+ (99.88, 99.90, 2, True),
+ (99.999, 100, 1, True),
+ (99.999, 100, 2, True),
+ (99.999, 100, 3, True),
])
-def test_should_fail_under(total, fail_under, result):
- assert should_fail_under(total, fail_under) == result
+def test_should_fail_under(total, fail_under, precision, result):
+ assert should_fail_under(float(total), float(fail_under), precision) == result