covimerage.CoveragePlugin not supported with PyTracer after upgrade to ubuntu 24.04
YouCompleteMe has been using covimerage for years. I'm trying to upgrade our CI to ubuntu 24.04 and am getting this error when running coverage:
coverage.py warning: Plugin file tracers (covimerage.CoveragePlugin) aren't supported with PyTracer
We have this in our test_requirements.txt:
coverage <5.0
click <8.0.0
covimerage >= 0.2.0
The click and coverage constraints were added to get covimerage working properly.
YCM pull request can be found here:
https://github.com/ycm-core/YouCompleteMe/pull/4260
Comparing the CI logs for master and for my pull request, there is a difference in what pip spits out when installing coverage.
Master:
Building wheels for collected packages: coverage
Building wheel for coverage (setup.py): started
Building wheel for coverage (setup.py): finished with status 'done'
Created wheel for coverage: filename=coverage-4.5.4-cp310-cp310-linux_x86_64.whl size=206478 sha256=7a52431a3d484347b24316972c7e5dc2835f8077195383b8d2e58415e1532c95
Stored in directory: /root/.cache/pip/wheels/24/3b/59/8b5b481efbac60694af4d7d281e8b27461384fcbd51ae53c8e
Successfully built coverage
Pull request
Building wheels for collected packages: coverage
Building wheel for coverage (setup.py): started
Building wheel for coverage (setup.py): finished with status 'done'
Created wheel for coverage: filename=coverage-4.5.4-py3-none-any.whl size=170500 sha256=cfc74044c055dc6b6888ccaf26f72c8f1f852202ca8ae29a64adb504a28972c0
Stored in directory: /root/.cache/pip/wheels/4e/09/63/e527151646074292e08bbcf04b648bd99319503c98f64eee20
Successfully built coverage
The -none-any variant of the wheel gets selected because there is no -cp312 variant, because coveragepy 4.5.4 is old.
The -none-any variant of the coveragepy wheel contains no coverage.tracer module.
That means no coverage.tracer.CTracer.
That means no plugins.
Essentially, this comes down to needing covimerage to support newer coveragepy.
I got it working. Here's the diff
diff --git a/covimerage/__init__.py b/covimerage/__init__.py
index d0ee675..1e8b0c7 100755
--- a/covimerage/__init__.py
+++ b/covimerage/__init__.py
@@ -195,22 +195,8 @@ class MergedProfiles(object):
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)
- 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
- except AttributeError:
- filename = str(data_file)
- logger.info('Writing coverage file %s.', filename)
- cov_data.write_fileobj(data_file)
+
+ cov_data.write()
return True
@@ -331,7 +317,7 @@ class Profile(object):
# are joined, while script lines might be spread
# across several lines (prefixed with \).
script_source = s_line.line
- if script_source != f_line.line:
+ if script_source != f_line.line.lstrip():
while True:
peek = script.lines[script_lnum + f_lnum + 1]
m = RE_CONTINUING_LINE.match(peek.line)
@@ -339,7 +325,7 @@ class Profile(object):
script_source += peek.line[m.end():]
script_lnum += 1
continue
- if script_source == f_line.line:
+ if script_source == f_line.line.lstrip():
break
return False
return True
@@ -379,13 +365,12 @@ class Profile(object):
if in_script or in_function:
lnum += 1
try:
- count, total_time, self_time = parse_count_and_times(line)
+ count, total_time, self_time, source_line = parse_count_and_times(line)
except Exception as exc:
logger.warning(
'Could not parse count/times (%s:%d, %r): %r.',
self._fstr, plnum, line, exc)
continue
- source_line = line[28:]
if in_script:
if count is None and RE_CONTINUING_LINE.match(source_line):
@@ -447,6 +432,7 @@ class Profile(object):
while functions:
prev_count = len(functions)
for f in functions:
+ #logger.warning('debug - %s - %s - %s', f.name, f.source, f.lines)
if self.map_function(f):
functions.remove(f)
new_count = len(functions)
@@ -481,7 +467,7 @@ class Profile(object):
# are joined, while script lines might be spread
# across several lines (prefixed with \).
script_source = s_line.line
- if script_source != f_line.line:
+ if script_source != f_line.line.lstrip():
while True:
try:
peek = script.lines[script_lnum + f_lnum + 1]
@@ -493,7 +479,7 @@ class Profile(object):
script_source += peek.line[m.end():]
script_lnum += 1
continue
- if script_source == f_line.line:
+ if script_source == f_line.line.lstrip():
break
logger.warning(
@@ -533,25 +519,13 @@ class Profile(object):
def parse_count_and_times(line):
- count = line[0:5]
- if count == '':
- return 0, None, None
- if count == ' ':
- count = None
- else:
- count = int(count)
- total_time = line[8:16]
- if total_time == ' ':
- total_time = None
- else:
- total_time = float(total_time)
- self_time = line[19:27]
- if self_time == ' ':
- self_time = None
- else:
- self_time = float(self_time)
-
- return count, total_time, self_time
+ line_regex = re.compile(r'^\s*(\d+)\s+([0-9.]+)\s+([0-9.]+)\s+(.+)$')
+ if match := line_regex.fullmatch( line ):
+ return int( match.group(1) ), float(match.group(2)), float(match.group(3)), match.group(4)
+ line_regex = re.compile(r'^\s*(\d+)\s+([0-9.]+)\s+(.+)$')
+ if match := line_regex.fullmatch( line ):
+ return int( match.group(1) ), float(match.group(2)), None, match.group(3)
+ return None, None, None, line.lstrip()
def coverage_init(reg, options):
diff --git a/covimerage/coveragepy.py b/covimerage/coveragepy.py
index a929de4..e64818c 100644
--- a/covimerage/coveragepy.py
+++ b/covimerage/coveragepy.py
@@ -7,16 +7,13 @@ import coverage
from ._compat import FileNotFoundError
from .exceptions import CoverageWrapperException
from .logger import logger
-from .utils import get_fname_and_fobj_and_str, is_executable_line
+from .utils import is_executable_line
RE_EXCLUDED = re.compile(
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
+from coverage.data import CoverageData as CoveragePyData
@attr.s(frozen=True)
@@ -33,22 +30,10 @@ class CoverageData(object):
if self.data_file is not None:
raise TypeError('data and data_file are mutually exclusive.')
return
- cov_data = CoveragePyData()
+ cov_data = CoveragePyData(self.data_file, suffix = True)
if self.data_file:
- fname, fobj, fstr = get_fname_and_fobj_and_str(self.data_file)
try:
- if 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:
- try:
- read_file = cov_data.read_file
- except AttributeError: # made private in coveragepy 5
- read_file = cov_data._read_file
- read_file(fname)
+ cov_data.read()
except coverage.CoverageException as exc:
raise CoverageWrapperException(
'Coverage could not read data_file: %s' % fstr,
@@ -188,6 +173,16 @@ class FileReporter(coverage.FileReporter):
return set(lines)
+class FileTracer(coverage.FileTracer):
+ def __init__(self, filename):
+ self._filename = filename
+ def source_filename(self):
+ return self._filename
+
+
class CoveragePlugin(coverage.CoveragePlugin):
def file_reporter(self, filename):
return FileReporter(filename)
+
+ def file_tracer(self, filename):
+ return FileTracer(filename) if filename.endswith( '.vim' ) else None
diff --git a/setup.py b/setup.py
index 8611061..94b2f2f 100755
--- a/setup.py
+++ b/setup.py
@@ -82,7 +82,7 @@ setup(
install_requires=[
'attrs>=16.1.0',
'click<7.1',
- 'coverage<5.0a6',
+ 'coverage',
],
extras_require={
'testing': DEPS_TESTING,