Tools: run_coverage.py: cope with lcov 2.x fatal error classes

lcov 2.x, as shipped in the Ubuntu 24.04 CI containers, promotes two
conditions the weekly coverage run always hits from warnings to fatal
errors:

 - "mismatched end line": two functions starting on the same source
   line with different end lines.  Every gtest TEST() body trips this,
   as the macro also defines the fixture constructor and destructor on
   the TEST() line, so both lcov --capture invocations abort.
 - "unused": a --remove pattern which matched nothing; ".waf*" never
   matches, so the pattern-removal step aborts.

Pass --ignore-errors for those classes on the affected invocations.
lcov 1.x rejects unknown error classes, so the arguments are only
emitted when lcov reports version 2 or later.
This commit is contained in:
Peter Barker
2026-09-23 08:37:53 +10:00
committed by Peter Barker
parent b3c52b2fcb
commit 25dc1c4640
+28
View File
@@ -7,6 +7,7 @@ Runs tests with gcov coverage support.
"""
import argparse
import os
import re
import shutil
import subprocess
import sys
@@ -38,6 +39,30 @@ class CoverageRunner(object):
self.failed_suites = []
self.start_time = time.time()
def lcov_ignore_errors(self, *classes):
"""Return --ignore-errors arguments for the given lcov error classes.
lcov 2.x promotes several conditions to fatal errors which 1.x
merely warned about (or did not check at all):
- "mismatch": two functions defined on the same source line
with different end lines. Every gtest TEST() body trips
this, as the macro also defines the fixture's constructor and
destructor on that line.
- "unused": an --exclude/--remove pattern which matched nothing.
lcov 1.x rejects unknown error classes outright, so the
arguments are only emitted for 2.x and later.
"""
try:
output = subprocess.run(["lcov", "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout
except OSError:
return []
match = re.search(r"version (\d+)", output)
if match is None or int(match.group(1)) < 2:
return []
return ["--ignore-errors", ",".join(classes)]
def progress(self, text) -> None:
"""Pretty printer."""
delta_time = time.time() - self.start_time
@@ -81,6 +106,7 @@ class CoverageRunner(object):
self.progress("Initializing Coverage with current build")
try:
result = subprocess.run(["lcov",
*self.lcov_ignore_errors("mismatch"),
"--no-external",
"--initial",
"--capture",
@@ -210,6 +236,7 @@ class CoverageRunner(object):
try:
self.progress("Capturing Coverage statistics")
subprocess.run(["lcov",
*self.lcov_ignore_errors("mismatch"),
"--no-external",
"--capture",
"--directory", root_dir,
@@ -236,6 +263,7 @@ class CoverageRunner(object):
# remove files we do not intentionally test:
self.progress("Removing unwanted coverage statistics")
subprocess.run(["lcov",
*self.lcov_ignore_errors("unused"),
"--remove", self.INFO_FILE,
".waf*",
root_dir + "/modules/*",