diff options
-rw-r--r-- | CHANGES.rst | 6 | ||||
-rw-r--r-- | CONTRIBUTORS.txt | 1 | ||||
-rw-r--r-- | coverage/cmdline.py | 5 | ||||
-rw-r--r-- | coverage/config.py | 4 | ||||
-rw-r--r-- | coverage/results.py | 24 | ||||
-rw-r--r-- | doc/config.rst | 10 | ||||
-rw-r--r-- | tests/test_cmdline.py | 12 | ||||
-rw-r--r-- | tests/test_process.py | 22 | ||||
-rw-r--r-- | tests/test_results.py | 35 |
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 |