diff --git a/.circleci/config.yml b/.circleci/config.yml index afa396fa..31520991 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,6 +33,12 @@ common: &common set +x fi jobs: + py37-coveragepy5: + <<: *common + docker: + - image: circleci/python:3.7 + environment: + TOXENV=py37-coveragepy5-coverage py37: <<: *common docker: @@ -80,6 +86,7 @@ workflows: version: 2 test: jobs: + - py37-coveragepy5 - py37 - py37-click6 - py36 diff --git a/covimerage/__init__.py b/covimerage/__init__.py index 0f0ad14f..c7fd63a4 100755 --- a/covimerage/__init__.py +++ b/covimerage/__init__.py @@ -186,14 +186,25 @@ def get_coveragepy_data(self): # TODO: move to CoverageWrapper def write_coveragepy_data(self, data_file='.coverage'): + import coverage + cov_data = self.get_coveragepy_data() - if not cov_data.line_counts(): + try: + line_counts = cov_data.line_counts() + except AttributeError: + line_counts = coverage.data.line_counts(cov_data) + if not line_counts: logger.warning('Not writing coverage file: no data to report!') return False if isinstance(data_file, string_types): logger.info('Writing coverage file %s.', data_file) - cov_data.write_file(data_file) + try: + write_file = cov_data.write_file + except AttributeError: + # coveragepy 5 + write_file = cov_data._write_file + write_file(data_file) else: try: filename = data_file.name diff --git a/covimerage/coveragepy.py b/covimerage/coveragepy.py index 7e32e1af..a929de48 100644 --- a/covimerage/coveragepy.py +++ b/covimerage/coveragepy.py @@ -13,27 +13,42 @@ r'"\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)') +try: + from coverage.data import CoverageJsonData as CoveragePyData +except ImportError: + from coverage.data import CoverageData as CoveragePyData + + @attr.s(frozen=True) class CoverageData(object): cov_data = attr.ib(default=None) data_file = attr.ib(default=None) def __attrs_post_init__(self): - if self.cov_data and self.data_file: - raise TypeError('data and data_file are mutually exclusive.') - if self.cov_data: - if not isinstance(self.cov_data, coverage.data.CoverageData): - raise TypeError( - 'data needs to be of type coverage.data.CoverageData') + if self.cov_data is not None: + if not isinstance(self.cov_data, CoveragePyData): + raise TypeError('data needs to be of type %s.%s' % ( + CoveragePyData.__module__, + CoveragePyData.__name__,)) + if self.data_file is not None: + raise TypeError('data and data_file are mutually exclusive.') return - cov_data = coverage.data.CoverageData() + cov_data = CoveragePyData() if self.data_file: fname, fobj, fstr = get_fname_and_fobj_and_str(self.data_file) try: if fobj: - cov_data.read_fileobj(fobj) + try: + read_fileobj = cov_data.read_fileobj + except AttributeError: # made private in coveragepy 5 + read_fileobj = cov_data._read_fileobj + read_fileobj(fobj) else: - cov_data.read_file(fname) + try: + read_file = cov_data.read_file + except AttributeError: # made private in coveragepy 5 + read_file = cov_data._read_file + read_file(fname) except coverage.CoverageException as exc: raise CoverageWrapperException( 'Coverage could not read data_file: %s' % fstr, @@ -73,6 +88,8 @@ def __attrs_post_init__(self): if not isinstance(self.data, CoverageData): data = CoverageData(cov_data=self.data, data_file=self.data_file) object.__setattr__(self, 'data', data) + # (confusing to have it twice) + # object.__setattr__(self, 'data_file', None) elif self.data_file: raise TypeError('data and data_file are mutually exclusive.') @@ -96,7 +113,12 @@ def _get_file_reporter(self, morf): config_file=True if self.config_file is None else self.config_file, ) cov_coverage._init() - cov_coverage.data = self.data.cov_data + if hasattr(cov_coverage, '_data'): + # coveragepy 5 + # TODO: get rid of intermediate handling of CoverageData? + cov_coverage._data = self.data.cov_data + else: + cov_coverage.data = self.data.cov_data return cov_coverage @handle_coverage_exceptions diff --git a/setup.py b/setup.py index 72bd54b3..30997d7e 100755 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ def run(self): install_requires=[ 'attrs', 'click', - 'coverage<=5', + 'coverage', ], extras_require={ 'testing': DEPS_TESTING, diff --git a/tests/test_cli.py b/tests/test_cli.py index 77abe356..b39fe9e7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -410,6 +410,7 @@ def test_coverage_plugin_for_annotate_merged_conditionals(runner, capfd, f.write('[run]\nplugins = covimerage') exit_code = call(['env', 'COVERAGE_FILE=%s' % tmpfile, + 'COVERAGE_STORAGE=json', # for coveragepy 5 'coverage', 'annotate', '--rcfile', coveragerc, '--directory', str(tmpdir)]) out, err = capfd.readouterr() diff --git a/tests/test_coveragepy.py b/tests/test_coveragepy.py index 9dedfedf..9b04d56d 100644 --- a/tests/test_coveragepy.py +++ b/tests/test_coveragepy.py @@ -60,20 +60,32 @@ def coverage_fileobj(): def test_coveragedata(coverage_fileobj): import coverage - from covimerage.coveragepy import CoverageData, CoverageWrapperException + from covimerage.coveragepy import ( + CoverageData, CoveragePyData, CoverageWrapperException) with pytest.raises(TypeError) as excinfo: - CoverageData(data_file='foo', cov_data='bar') + CoverageData(data_file='foo', cov_data=CoveragePyData()) assert excinfo.value.args == ( 'data and data_file are mutually exclusive.',) data = CoverageData() - assert isinstance(data.cov_data, coverage.data.CoverageData) + try: + from coverage.data import CoverageJsonData + except ImportError: + assert isinstance(data.cov_data, coverage.data.CoverageData) + else: + assert isinstance(data.cov_data, CoverageJsonData) with pytest.raises(TypeError) as excinfo: CoverageData(cov_data='foo') - assert excinfo.value.args == ( - 'data needs to be of type coverage.data.CoverageData',) + try: + from coverage.data import CoverageJsonData + except ImportError: + assert excinfo.value.args == ( + 'data needs to be of type coverage.data.CoverageData',) + else: + assert excinfo.value.args == ( + 'data needs to be of type coverage.data.CoverageJsonData',) with pytest.raises(CoverageWrapperException) as excinfo: CoverageData(data_file='/does/not/exist') @@ -106,7 +118,12 @@ def test_coveragedata_empty(covdata_empty): f = StringIO() data = CoverageData() - data.cov_data.write_fileobj(f) + try: + write_fileobj = data.cov_data.write_fileobj + except AttributeError: + # coveragepy 5 + write_fileobj = data.cov_data._write_fileobj + write_fileobj(f) f.seek(0) assert f.read() == covdata_empty @@ -114,13 +131,13 @@ def test_coveragedata_empty(covdata_empty): def test_coveragewrapper(coverage_fileobj, devnull): import coverage from covimerage.coveragepy import ( - CoverageData, CoverageWrapper, CoverageWrapperException) + CoverageData, CoveragePyData, CoverageWrapper, CoverageWrapperException) cov_data = CoverageWrapper() assert cov_data.lines == {} assert isinstance(cov_data.data, CoverageData) - cov_data = CoverageWrapper(data=coverage.data.CoverageData()) + cov_data = CoverageWrapper(data=CoveragePyData()) assert cov_data.lines == {} assert isinstance(cov_data.data, CoverageData) @@ -128,7 +145,7 @@ def test_coveragewrapper(coverage_fileobj, devnull): CoverageWrapper(data_file='foo', data='bar') with pytest.raises(TypeError): - CoverageWrapper(data_file='foo', data=CoverageData()) + CoverageWrapper(data_file='foo', data=CoveragePyData()) cov = CoverageWrapper(data_file=coverage_fileobj) with pytest.raises(attr.exceptions.FrozenInstanceError): @@ -139,7 +156,11 @@ def test_coveragewrapper(coverage_fileobj, devnull): 3, 8, 9, 11, 13, 14, 15, 17, 23]} assert isinstance(cov._cov_obj, coverage.control.Coverage) - assert cov._cov_obj.data is cov.data.cov_data + if hasattr(cov._cov_obj, '_data'): + # coveragepy 5 + assert cov._cov_obj._data is cov.data.cov_data + else: + assert cov._cov_obj.data is cov.data.cov_data with pytest.raises(CoverageWrapperException) as excinfo: CoverageWrapper(data_file=devnull.name) @@ -158,6 +179,17 @@ def test_coveragewrapper(coverage_fileobj, devnull): e.message, e.orig_exc) +def test_coveragewrapper_requires_jsondata(): + pytest.importorskip('coverage.sqldata') + from covimerage.coveragepy import CoverageWrapper + + with pytest.raises(TypeError) as excinfo: + CoverageWrapper(data=coverage.sqldata.CoverageSqliteData()) + + assert excinfo.value.args[0] == ( + 'data needs to be of type coverage.data.CoverageJsonData') + + def test_coveragewrapper_uses_config_file(tmpdir, capfd): from covimerage.coveragepy import CoverageWrapper, CoverageWrapperException diff --git a/tests/test_main.py b/tests/test_main.py index 5733ba09..06bd5a7b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -380,8 +380,12 @@ def test_merged_profiles_get_coveragepy_data(): m = MergedProfiles([]) cov_data = m.get_coveragepy_data() - assert isinstance(cov_data, coverage.CoverageData) - assert repr(cov_data) == '' + try: + from coverage.data import CoverageJsonData + except ImportError: + assert isinstance(cov_data, coverage.CoverageData) + else: + assert isinstance(cov_data, CoverageJsonData) def test_merged_profiles_write_coveragepy_data_handles_fname_and_fobj( diff --git a/tox.ini b/tox.ini index 24ba57e9..bdddbf15 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,8 @@ changedir = integration: {envtmpdir} deps = click6: click<7 + coveragepy4: coverage<5 + coveragepy5: coverage>=5<6 [testenv:checkqa] extras = qa