diff --git a/cov-core/cov_core.py b/cov-core/cov_core.py index b9618050..dd5a2b57 100644 --- a/cov-core/cov_core.py +++ b/cov-core/cov_core.py @@ -78,8 +78,12 @@ def sep(stream, s, txt): def summary(self, stream): """Produce coverage reports.""" + total = 0 + if self.cov_report == ['']: - return + with open(os.devnull, 'w') as null: + total = self.cov.report(show_missing=True, ignore_errors=True, file=null) + return total # Output coverage section header. if len(self.node_descs) == 1: @@ -92,21 +96,21 @@ def summary(self, stream): # Produce terminal report if wanted. if 'term' in self.cov_report or 'term-missing' in self.cov_report: show_missing = 'term-missing' in self.cov_report - self.cov.report(show_missing=show_missing, ignore_errors=True, file=stream) + total = self.cov.report(show_missing=show_missing, ignore_errors=True, file=stream) # Produce annotated source code report if wanted. if 'annotate' in self.cov_report: - self.cov.annotate(ignore_errors=True) + total = self.cov.annotate(ignore_errors=True) stream.write('Coverage annotated source written next to source\n') # Produce html report if wanted. if 'html' in self.cov_report: - self.cov.html_report(ignore_errors=True) + total = self.cov.html_report(ignore_errors=True) stream.write('Coverage HTML written to dir %s\n' % self.cov.config.html_dir) # Produce xml report if wanted. if 'xml' in self.cov_report: - self.cov.xml_report(ignore_errors=True) + total = self.cov.xml_report(ignore_errors=True) stream.write('Coverage XML written to file %s\n' % self.cov.config.xml_output) # Report on any failed slaves. @@ -117,6 +121,8 @@ def summary(self, stream): for node in self.failed_slaves: stream.write('%s\n' % node.gateway.id) + return total + class Central(CovController): """Implementation for centralised operation.""" diff --git a/pytest-cov/pytest_cov.py b/pytest-cov/pytest_cov.py index d9b917b8..edfe73b2 100644 --- a/pytest-cov/pytest_cov.py +++ b/pytest-cov/pytest_cov.py @@ -8,6 +8,10 @@ import cov_core_init +class CoverageError(Exception): + '''Indicates that our coverage is too low''' + + def pytest_addoption(parser): """Add options to control coverage.""" @@ -34,6 +38,8 @@ def pytest_addoption(parser): dest='no_cov_on_fail', help='do not report coverage if test run fails, ' 'default: False') + group.addoption('--cov-min', action='store', metavar='MIN', type='int', + help='Fail if the total coverage is less than MIN.') @pytest.mark.tryfirst @@ -148,7 +154,13 @@ def pytest_terminal_summary(self, terminalreporter): if self.cov_controller is None: return if not (self.failed and self.options.no_cov_on_fail): - self.cov_controller.summary(terminalreporter.writer) + total = self.cov_controller.summary(terminalreporter.writer) + assert total is not None, 'Test coverage should never be `None`' + cov_min = self.options.cov_min + if cov_min is not None and total < cov_min: + raise CoverageError(('Required test coverage of %d%% not ' + 'reached. Total coverage: %.2f%%') + % (self.options.cov_min, total)) def pytest_runtest_setup(self, item): if os.getpid() != self.pid: diff --git a/pytest-cov/test_pytest_cov.py b/pytest-cov/test_pytest_cov.py index 6da07dd9..5dacc1c2 100644 --- a/pytest-cov/test_pytest_cov.py +++ b/pytest-cov/test_pytest_cov.py @@ -113,6 +113,42 @@ def test_central(testdir): assert result.ret == 0 +def test_cov_min_100(testdir): + script = testdir.makepyfile(SCRIPT) + + result = testdir.runpytest('-v', + '--cov', '--cov-source=%s' % script.dirpath(), + '--cov-report=term-missing', + '--cov-min=100', + script) + + assert result.ret == 1 + + +def test_cov_min_50(testdir): + script = testdir.makepyfile(SCRIPT) + + result = testdir.runpytest('-v', + '--cov', '--cov-source=%s' % script.dirpath(), + '--cov-report=term-missing', + '--cov-min=50', + script) + + assert result.ret == 0 + + +def test_cov_min_no_report(testdir): + script = testdir.makepyfile(SCRIPT) + + result = testdir.runpytest('-v', + '--cov', '--cov-source=%s' % script.dirpath(), + '--cov-report=', + '--cov-min=50', + script) + + assert result.ret == 0 + + def test_central_nonspecific(testdir): script = testdir.makepyfile(SCRIPT)