diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index d228d5b5..fdd11763 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -130,8 +130,7 @@ def summary(self, stream): 'file': stream, } skip_covered = isinstance(self.cov_report, dict) and 'skip-covered' in self.cov_report.values() - if hasattr(coverage, 'version_info') and coverage.version_info[0] >= 4: - options.update({'skip_covered': skip_covered or None}) + options.update({'skip_covered': skip_covered or None}) with _backup(self.cov, "config"): total = self.cov.report(**options) diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index d84a6dbc..194ebea7 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -4,6 +4,7 @@ import warnings import pytest +import coverage from coverage.misc import CoverageException from . import compat @@ -48,6 +49,16 @@ def validate_fail_under(num_str): return float(num_str) +def validate_contexts(arg): + if coverage.version_info <= (5, 0): + msg = 'Contexts are only supported with coverage.py >= 5.x' + raise argparse.ArgumentTypeError(msg) + if arg != "test": + msg = '--cov-contexts=test is the only supported value' + raise argparse.ArgumentTypeError(msg) + return arg + + class StoreReport(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): report_type, file = values @@ -88,6 +99,9 @@ def pytest_addoption(parser): 'Default: False') group.addoption('--cov-branch', action='store_true', default=None, help='Enable branch coverage.') + group.addoption('--cov-contexts', action='store', metavar='CONTEXT', + type=validate_contexts, + help='Dynamic contexts to use. "test" for now.') def _prepare_cov_source(cov_source): @@ -151,6 +165,9 @@ def __init__(self, options, pluginmanager, start=True): elif start: self.start(engine.Central) + if getattr(options, 'cov_contexts', None) == 'test': + pluginmanager.register(TestContextPlugin(self.cov_controller.cov), '_cov_contexts') + # worker is started in pytest hook def start(self, controller_cls, config=None, nodeid=None): @@ -308,6 +325,24 @@ def pytest_runtest_call(self, item): yield +class TestContextPlugin(object): + def __init__(self, cov): + self.cov = cov + + def pytest_runtest_setup(self, item): + self.switch_context(item, 'setup') + + def pytest_runtest_teardown(self, item): + self.switch_context(item, 'teardown') + + def pytest_runtest_call(self, item): + self.switch_context(item, 'run') + + def switch_context(self, item, when): + context = "{item.nodeid}|{when}".format(item=item, when=when) + self.cov.switch_context(context) + + @pytest.fixture def no_cover(): """A pytest fixture to disable coverage.""" diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py index 6d0e7e78..9f78ba19 100644 --- a/tests/test_pytest_cov.py +++ b/tests/test_pytest_cov.py @@ -1,9 +1,9 @@ import glob import os import platform +import sqlite3 import subprocess import sys -from distutils.version import StrictVersion from itertools import chain import coverage @@ -25,7 +25,7 @@ import pytest_cov.plugin from pytest_cov import compat -coverage, platform, StrictVersion # required for skipif mark on test_cov_min_from_coveragerc +coverage, platform # required for skipif mark on test_cov_min_from_coveragerc max_worker_restart_0 = "--max-" + compat.worker + "-restart=0" @@ -468,7 +468,6 @@ def test_central_nonspecific(testdir, prop): assert result.ret == 0 -@pytest.mark.skipif('StrictVersion(coverage.__version__) <= StrictVersion("3.8")') def test_cov_min_from_coveragerc(testdir): script = testdir.makepyfile(SCRIPT) testdir.tmpdir.join('.coveragerc').write(""" @@ -1636,7 +1635,6 @@ def test_basic(): SKIP_COVERED_RESULT = '1 file skipped due to complete coverage.' -@pytest.mark.skipif('StrictVersion(coverage.__version__) < StrictVersion("4.0")') @pytest.mark.parametrize('report_option', [ 'term-missing:skip-covered', 'term:skip-covered']) @@ -1651,7 +1649,6 @@ def test_skip_covered_cli(testdir, report_option): result.stdout.fnmatch_lines([SKIP_COVERED_RESULT]) -@pytest.mark.skipif('StrictVersion(coverage.__version__) < StrictVersion("4.0")') def test_skip_covered_coveragerc_config(testdir): testdir.makefile('', coveragerc=SKIP_COVERED_COVERAGERC) script = testdir.makepyfile(SKIP_COVERED_TEST) @@ -1932,3 +1929,59 @@ def test_cov_and_no_cov(testdir): script) assert result.ret == 0 + + +CONTEXTFUL_TESTS = '''\ +import unittest + +def test_one(): + assert 1 == 1 + +def test_two(): + assert 2 == 2 + +class OldStyleTests(unittest.TestCase): + def setUp(self): + self.three = 3 + def tearDown(self): + self.three = None + def test_three(self): + assert self.three == 3 +''' + +@pytest.mark.skipif("coverage.version_info < (5, 0)") +@xdist_params +def test_contexts(testdir, opts): + script = testdir.makepyfile(CONTEXTFUL_TESTS) + result = testdir.runpytest('-v', + '--cov=%s' % script.dirpath(), + '--cov-contexts=test', + script, + *opts.split() + ) + result.stdout.fnmatch_lines([ + 'test_contexts* 100%*', + ]) + con = sqlite3.connect(".coverage") + cur = con.cursor() + contexts = set(r[0] for r in cur.execute("select context from context")) + assert contexts == { + '', + 'test_contexts.py::test_two|run', + 'test_contexts.py::test_one|run', + 'test_contexts.py::OldStyleTests::test_three|run', + } + + +@pytest.mark.skipif("coverage.version_info >= (5, 0)") +def test_contexts_not_supported(testdir): + script = testdir.makepyfile(CONTEXTFUL_TESTS) + result = testdir.runpytest('-v', + '--cov=%s' % script.dirpath(), + '--cov-contexts=test', + script, + ) + result.stderr.fnmatch_lines([ + '*argument --cov-contexts: Contexts are only supported with coverage.py >= 5.x', + ]) + assert result.ret != 0