Skip to content

Commit

Permalink
feat: support coverage>=5.0 (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheKevJames committed Dec 31, 2019
1 parent c23508d commit 4a91740
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 37 deletions.
82 changes: 71 additions & 11 deletions .circleci/config.yml
Expand Up @@ -13,13 +13,26 @@ jobs:
# TODO: figure out `<<parameters.docker_image>>.replace('.','')`
tox_environment:
type: string
cov_version:
default: ""
type: string
steps:
- run: apk add --no-cache gcc git libffi-dev musl-dev openssh-client openssl-dev
- checkout
- run: pip install tox tox-factor
- run: tox -f <<parameters.tox_environment>>
- run: tox -e coveralls
- unless:
condition: <<parameters.cov_version>>
steps:
- run: tox -f <<parameters.tox_environment>>
- run: tox -e coveralls41
- when:
condition: <<parameters.cov_version>>
steps:
- run: tox -f <<parameters.tox_environment>>-cov<<parameters.cov_version>>
- run: tox -e coveralls<<parameters.cov_version>>

# TODO: by introspecting `docker_image` (or `tox_environment`), we could
# these job definitions
toxpypy:
docker:
- image: pypy:<<parameters.docker_image>>
Expand All @@ -29,11 +42,22 @@ jobs:
# TODO: figure out `<<parameters.docker_image>>.replace('.','')`
tox_environment:
type: string
cov_version:
default: ""
type: string
steps:
- checkout
- run: pip install tox tox-factor
- run: tox -f <<parameters.tox_environment>>
- run: tox -e coveralls
- unless:
condition: <<parameters.cov_version>>
steps:
- run: tox -f <<parameters.tox_environment>>
- run: tox -e coveralls41
- when:
condition: <<parameters.cov_version>>
steps:
- run: tox -f <<parameters.tox_environment>>-cov<<parameters.cov_version>>
- run: tox -e coveralls<<parameters.cov_version>>

workflows:
run-jobs:
Expand All @@ -54,17 +78,35 @@ workflows:
docker_image: '3.5'
tox_environment: py35
- toxpy:
name: test-py3.6
name: test-py3.6-cov4
docker_image: '3.6'
tox_environment: py36
cov_version: '41'
- toxpy:
name: test-py3.6-cov5
docker_image: '3.6'
tox_environment: py36
cov_version: '5'
- toxpy:
name: test-py3.7
name: test-py3.7-cov4
docker_image: '3.7'
tox_environment: py37
cov_version: '41'
- toxpy:
name: test-py3.7-cov5
docker_image: '3.7'
tox_environment: py37
cov_version: '5'
- toxpy:
name: test-py3.8-cov4
docker_image: '3.8'
tox_environment: py38
cov_version: '41'
- toxpy:
name: test-py3.8
name: test-py3.8-cov5
docker_image: '3.8'
tox_environment: py38
cov_version: '5'

- toxpypy:
name: test-pypy2-5
Expand All @@ -80,14 +122,32 @@ workflows:
tox_environment: pypy

- toxpypy:
name: test-pypy3-5
name: test-pypy3-5-cov4
docker_image: 3-5
tox_environment: pypy3
cov_version: '41'
- toxpypy:
name: test-pypy3-6
name: test-pypy3-5-cov5
docker_image: 3-5
tox_environment: pypy3
cov_version: '5'
- toxpypy:
name: test-pypy3-6-cov4
docker_image: 3-6
tox_environment: pypy3
cov_version: '41'
- toxpypy:
name: test-pypy3-6-cov5
docker_image: 3-6
tox_environment: pypy3
cov_version: '5'
- toxpypy:
name: test-pypy3-7
name: test-pypy3-7-cov4
docker_image: 3-7
tox_environment: pypy37
tox_environment: pypy3
cov_version: '41'
- toxpypy:
name: test-pypy3-7-cov5
docker_image: 3-7
tox_environment: pypy3
cov_version: '5'
2 changes: 1 addition & 1 deletion coveralls/api.py
Expand Up @@ -265,7 +265,7 @@ def get_coverage(self):
else:
workman.get_data()

return CoverallReporter(workman, workman.config).report()
return CoverallReporter(workman, workman.config).coverage

@staticmethod
def debug_bad_encoding(data):
Expand Down
131 changes: 113 additions & 18 deletions coveralls/reporter.py
Expand Up @@ -7,51 +7,139 @@
from coverage.misc import NoSource
from coverage.misc import NotPython
from coverage.phystokens import source_encoding
from coverage.report import Reporter

from .exception import CoverallsException


log = logging.getLogger('coveralls.reporter')


class CoverallReporter(Reporter):
class CoverallReporter(object):
"""Custom coverage.py reporter for coveralls.io"""

def __init__(self, *args, **kwargs):
self.source_files = []
super(CoverallReporter, self).__init__(*args, **kwargs)
def __init__(self, cov, conf):
self.coverage = []
self.report(cov, conf)

def report(self, morfs=None):
def report5(self, cov):
# N.B. this method is 99% copied from the coverage source code;
# unfortunately, the coverage v5 style of `get_analysis_to_report`
# errors out entirely if any source file has issues -- which would be a
# breaking change for us. In the interest of backwards compatibility,
# I've copied their code here so we can maintain the same `coveralls`
# API regardless of which `coverage` version is being used.
#
# TODO: deprecate the relevant APIs so we can just use the coverage
# public API directly.
#
# from coverage.report import get_analysis_to_report
# try:
# for cu, analyzed in get_analysis_to_report(cov, None):
# self.parse_file(cu, analyzed)
# except NoSource:
# # Note that this behavior must necessarily change between
# # coverage<5 and coverage>=5, as we are no longer interweaving
# # with get_analysis_to_report (a single exception breaks the
# # whole loop)
# log.warning('No source for at least one file')
# except NotPython:
# # Note that this behavior must necessarily change between
# # coverage<5 and coverage>=5, as we are no longer interweaving
# # with get_analysis_to_report (a single exception breaks the
# # whole loop)
# log.warning('A source file is not python')
# except CoverageException as e:
# if str(e) != 'No data to report.':
# raise

from coverage.files import FnmatchMatcher, prep_patterns

# get_analysis_to_report starts here; changes marked with TODOs
file_reporters = cov._get_file_reporters(None) # pylint: disable=W0212
config = cov.config

if config.report_include:
matcher = FnmatchMatcher(prep_patterns(config.report_include))
file_reporters = [fr for fr in file_reporters
if matcher.match(fr.filename)]

if config.report_omit:
matcher = FnmatchMatcher(prep_patterns(config.report_omit))
file_reporters = [fr for fr in file_reporters
if not matcher.match(fr.filename)]

# TODO: deprecate changes
# if not file_reporters:
# raise CoverageException("No data to report.")

for fr in sorted(file_reporters):
try:
analysis = cov._analyze(fr) # pylint: disable=W0212
except NoSource:
if not config.ignore_errors:
# TODO: deprecate changes
# raise
log.warning('No source for %s', fr.filename)
except NotPython:
# Only report errors for .py files, and only if we didn't
# explicitly suppress those errors.
# NotPython is only raised by PythonFileReporter, which has a
# should_be_python() method.
if fr.should_be_python():
if config.ignore_errors:
msg = "Couldn't parse Python file '{}'".format(
fr.filename)
cov._warn(msg, # pylint: disable=W0212
slug="couldnt-parse")
else:
# TODO: deprecate changes
# raise
log.warning('Source file is not python %s',
fr.filename)
else:
# TODO: deprecate changes (well, this one is fine /shrug)
# yield (fr, analysis)
self.parse_file(fr, analysis)

def report(self, cov, conf, morfs=None):
"""
Generate a part of json report for coveralls
`morfs` is a list of modules or filenames.
`outfile` is a file object to write the json to.
"""
# pylint: disable=too-many-branches
try:
from coverage.report import Reporter
self.reporter = Reporter(cov, conf)
except ImportError: # coverage >= 5.0
return self.report5(cov)

units = None
if hasattr(self, 'find_code_units'):
self.find_code_units(morfs)
if hasattr(self.reporter, 'find_code_units'):
self.reporter.find_code_units(morfs)
else:
units = self.find_file_reporters(morfs)
units = self.reporter.find_file_reporters(morfs)

if units is None:
if hasattr(self, 'code_units'):
units = self.code_units
if hasattr(self.reporter, 'code_units'):
units = self.reporter.code_units
else:
units = self.file_reporters
units = self.reporter.file_reporters

for cu in units:
try:
analyzed = self.coverage._analyze(cu) # pylint: disable=W0212
_fn = self.reporter.coverage._analyze # pylint: disable=W0212
analyzed = _fn(cu)
self.parse_file(cu, analyzed)
except NoSource:
if not self.config.ignore_errors:
if not self.reporter.config.ignore_errors:
log.warning('No source for %s', cu.filename)
except NotPython:
# Only report errors for .py files, and only if we didn't
# explicitly suppress those errors.
if cu.should_be_python() and not self.config.ignore_errors:
if (cu.should_be_python()
and not self.reporter.config.ignore_errors):
log.warning('Source file is not python %s', cu.filename)
except KeyError:
version = [int(x) for x in __version__.split('.')]
Expand All @@ -66,7 +154,7 @@ def report(self, morfs=None):

raise

return self.source_files
return self.coverage

@staticmethod
def get_hits(line_num, analysis):
Expand Down Expand Up @@ -101,7 +189,14 @@ def get_arcs(analysis):
if not analysis.has_arcs():
return None

branch_lines = analysis.branch_lines()
if not hasattr(analysis, 'branch_lines'):
# N.B. switching to the public method analysis.missing_branch_arcs
# would work for half of what we need, but there doesn't seem to be
# an equivalent analysis.executed_branch_arcs
branch_lines = analysis._branch_lines() # pylint: disable=W0212
else:
branch_lines = analysis.branch_lines()

branches = []

for l1, l2 in analysis.arcs_executed():
Expand Down Expand Up @@ -160,4 +255,4 @@ def parse_file(self, cu, analysis):
if branches:
results['branches'] = branches

self.source_files.append(results)
self.coverage.append(results)
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -33,7 +33,7 @@
],
},
install_requires=[
'coverage>=3.6,<5.0',
'coverage>=3.6,<6.0',
'docopt>=0.6.1',
'requests>=1.0.0',
],
Expand Down
18 changes: 12 additions & 6 deletions tox.ini
Expand Up @@ -5,9 +5,9 @@ envlist = py{27,34,35,py}-cov{3,4,41}-{default,pyyaml},py{36,37,38,py3}-cov{41,5
python =
2.7: py27
3.5: py35
3.6: py36
3.7: py37
3.8: py38
3.6: py36-cov5
3.7: py37-cov5
3.8: py38-cov5

[testenv]
passenv = *
Expand All @@ -20,13 +20,19 @@ deps =
cov3: coverage<4.0
cov4: coverage>=4.0,<4.1
cov41: coverage>=4.1,<5.0
cov5: coverage==5.0b1
cov5: coverage>=5.0,<6.0
commands =
coverage run --branch --source=coveralls -m pytest tests/
coverage report -m

[testenv:coveralls]
[testenv:coveralls41]
deps =
coverage<5.0
coverage>=4.1,<5.0
commands =
coveralls --verbose

[testenv:coveralls5]
deps =
coverage>=5.0,<6.0
commands =
coveralls --verbose

0 comments on commit 4a91740

Please sign in to comment.