Skip to content

Commit

Permalink
allow failure reports even with insufficient coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
davidszotten committed May 11, 2016
1 parent 0696bc7 commit 3656515
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 18 deletions.
59 changes: 42 additions & 17 deletions src/pytest_cov/plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Coverage plugin for pytest."""
import os
from StringIO import StringIO

import pytest
import argparse
from coverage.misc import CoverageException
Expand Down Expand Up @@ -117,6 +119,8 @@ def __init__(self, options, pluginmanager, start=True):
self.pid = None
self.cov = None
self.cov_controller = None
self.cov_report = StringIO()
self.cov_total = None
self.failed = False
self._started = False
self.options = options
Expand Down Expand Up @@ -180,30 +184,51 @@ def pytest_testnodedown(self, node, error):
self.cov_controller.testnodedown(node, error)
pytest_testnodedown.optionalhook = True

def pytest_sessionfinish(self, session, exitstatus):
"""Delegate to our implementation."""
self.failed = exitstatus != 0
def _should_report(self):
return not (self.failed and self.options.no_cov_on_fail)

def _failed_cov_total(self):
cov_fail_under = self.options.cov_fail_under
return cov_fail_under is not None and self.cov_total < cov_fail_under

# we need to wrap pytest_runtestloop. by the time pytest_sessionfinish
# runs, it's too late to set testsfailed
@pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(self, session):
outcome = yield

self.failed = bool(session.testsfailed)
if self.cov_controller is not None:
self.cov_controller.finish()

def pytest_terminal_summary(self, terminalreporter):
"""Delegate to our implementation."""
if self.cov_controller is None:
return
if not (self.failed and self.options.no_cov_on_fail):
if self._should_report():
try:
total = self.cov_controller.summary(terminalreporter.writer)
self.cov_total = self.cov_controller.summary(self.cov_report)
except CoverageException as exc:
terminalreporter.writer.write('Failed to generate report: %s\n' % exc)
total = 0
assert total is not None, 'Test coverage should never be `None`'
cov_fail_under = self.options.cov_fail_under
if cov_fail_under is not None and total < cov_fail_under:
raise pytest.UsageError(
'Required test coverage of %d%% not '
'reached. Total coverage: %.2f%%\n'
% (self.options.cov_fail_under, total)
'Failed to generate report: %s\n' % exc
)
assert self.cov_total is not None, 'Test coverage should never be `None`'
if self._failed_cov_total():
# make sure we get the EXIT_TESTSFAILED exit code
session.testsfailed += 1

def pytest_terminal_summary(self, terminalreporter):
if self.cov_controller is None:
return
if self.cov_total is None:
# report generation failed (error raised above)
return

terminalreporter.write('\n' + self.cov_report.getvalue())
if self._should_report() and self._failed_cov_total():
markup = {'red': True, 'bold': True}
msg = (
'Required test coverage of %d%% not '
'reached. Total coverage: %.2f%%'
% (self.options.cov_fail_under, self.cov_total)
)
terminalreporter.write(msg + '\n', **markup)

def pytest_runtest_setup(self, item):
if os.getpid() != self.pid:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pytest_cov.py
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ def test_dist_boxed(testdir):

def test_not_started_plugin_does_not_fail(testdir):
plugin = pytest_cov.plugin.CovPlugin(None, None, start=False)
plugin.pytest_sessionfinish(None, None)
plugin.pytest_runtestloop(None)
plugin.pytest_terminal_summary(None)


Expand Down

0 comments on commit 3656515

Please sign in to comment.