feat(scripts/check_gcov_coverage): add analysis of specified file paths (#9264)
Arduino Lint / lint (push) Has been cancelled
Build Examples with C++ Compiler / build-examples (push) Has been cancelled
MicroPython CI / Build esp32 port (push) Has been cancelled
MicroPython CI / Build rp2 port (push) Has been cancelled
MicroPython CI / Build stm32 port (push) Has been cancelled
MicroPython CI / Build unix port (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build ESP IDF ESP32S3 (push) Has been cancelled
C/C++ CI / Run tests with 32bit build (push) Has been cancelled
C/C++ CI / Run tests with 64bit build (push) Has been cancelled
BOM Check / bom-check (push) Has been cancelled
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Has been cancelled
Verify the widget property name / verify-property-name (push) Has been cancelled
Verify code formatting / verify-formatting (push) Has been cancelled
Compare file templates with file names / template-check (push) Has been cancelled
Build docs / build-and-deploy (push) Has been cancelled
Test API JSON generator / Test API JSON (push) Has been cancelled
Install LVGL using CMake / build-examples (push) Has been cancelled
Check Makefile / Build using Makefile (push) Has been cancelled
Check Makefile for UEFI / Build using Makefile for UEFI (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/benchmark_results_comment/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/filter_docker_logs/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/serialize_results/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 32b - lv_conf_perf32b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 64b - lv_conf_perf64b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Save PR Number (push) Has been cancelled
Hardware Performance Test / Hardware Performance Benchmark (push) Has been cancelled
Hardware Performance Test / HW Benchmark - Save PR Number (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_32B - Ubuntu (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_64B - Ubuntu (push) Has been cancelled
Port repo release update / run-release-branch-updater (push) Has been cancelled
Verify Font License / verify-font-license (push) Has been cancelled
Verify Kconfig / verify-kconfig (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

Signed-off-by: pengyiqiang <pengyiqiang@xiaomi.com>
Co-authored-by: pengyiqiang <pengyiqiang@xiaomi.com>
This commit is contained in:
VIFEX
2025-11-20 20:24:20 +08:00
committed by GitHub
parent a0635f24c8
commit d91691ce4b
+154 -34
View File
@@ -8,7 +8,7 @@ import json
import re import re
import sys import sys
import os import os
from typing import Dict, Set, Tuple, List from typing import Dict, Set, Tuple, List, Optional
def create_argument_parser() -> argparse.ArgumentParser: def create_argument_parser() -> argparse.ArgumentParser:
@@ -24,6 +24,15 @@ def create_argument_parser() -> argparse.ArgumentParser:
default="HEAD", default="HEAD",
) )
parser.add_argument(
"--path",
metavar="PATH",
help=(
"Analyze coverage for a single file or a directory (relative or absolute path). "
"When specified, --commit is ignored."
),
)
parser.add_argument( parser.add_argument(
"--fail-under", "--fail-under",
type=float, type=float,
@@ -212,6 +221,126 @@ def check_commit_coverage(
return covered_lines, total_new_lines, uncovered_lines, skipped_noncoverable return covered_lines, total_new_lines, uncovered_lines, skipped_noncoverable
def check_path_coverage(path: str, root: str) -> Tuple[int, int, List[Tuple[str, int]]]:
"""
Compute coverage for a specific file or directory.
Returns: (covered_lines, total_coverable_lines, uncovered_lines)
Notes:
- Only lines reported by gcovr as coverable are counted toward totals.
- If PATH is a directory, coverage is aggregated over all coverable files within it.
- If PATH is a file, coverage is computed only for that file.
"""
# Normalize input path
abs_path = os.path.abspath(path)
if not os.path.exists(abs_path):
print(f"Error: The specified path does not exist: {abs_path}", file=sys.stderr)
sys.exit(1)
# Ensure we operate from repo root to construct relative POSIX paths
root = os.path.abspath(root)
# Build relative POSIX target(s)
rel = os.path.relpath(abs_path, root)
rel_posix = rel.replace(os.path.sep, "/")
print("Getting coverage data...")
coverage_data = get_coverage_data(root)
covered = 0
total = 0
uncovered: List[Tuple[str, int]] = []
if os.path.isdir(abs_path):
# Directory scope: include all files under this directory
scoped_files = {}
for f, lines in coverage_data.items():
# Convert both to absolute paths for comparison
f_abs = os.path.abspath(os.path.join(root, f.replace("/", os.path.sep)))
# If the common path of abs_path and f_abs is abs_path, f is under abs_path
if os.path.commonpath([abs_path, f_abs]) == abs_path:
scoped_files[f] = lines
print(f"Found {len(scoped_files)} coverable file(s) under: {rel_posix}")
for filename, line_map in sorted(scoped_files.items()):
for lineno, count in line_map.items():
total += 1
if count > 0:
covered += 1
else:
uncovered.append((filename, lineno))
else:
# File scope
if rel_posix in coverage_data:
line_map = coverage_data[rel_posix]
print(f"Found coverable file: {rel_posix}")
for lineno, count in line_map.items():
total += 1
if count > 0:
covered += 1
else:
uncovered.append((rel_posix, lineno))
else:
print(
f"Warning: No coverable lines found for '{rel_posix}' in gcovr output."
)
return covered, total, uncovered
def report_coverage(
header: str,
total: int,
covered: int,
uncovered: List[Tuple[str, int]],
fail_under: float,
*,
total_label: str,
skipped_noncoverable: Optional[int] = None,
) -> int:
"""
Print a standardized coverage report and return exit code (0/1).
- header: text to show in the report title (e.g., "commit <hash>", "'<path>'")
- total_label: label to use for the total line count (e.g.,
"New coverable lines (per gcovr)" for commit mode, or
"Coverable lines (per gcovr)" for path mode)
- skipped_noncoverable: when provided, prints the skipped non-coverable count
"""
title = f" Coverage analysis results for {header} "
separator = "=" * len(title)
print(f"\n{separator}\n{title}\n{separator}")
print(f"{total_label}: {total}")
print(f"Covered lines: {covered}")
print(f"Uncovered lines: {len(uncovered)}")
if skipped_noncoverable is not None:
print(f"Skipped non-coverable changed lines: {skipped_noncoverable}")
retval = 0
if total > 0:
coverage_percent = (covered / total) * 100
print(f"Coverage: {coverage_percent:.2f}%")
if coverage_percent < fail_under:
print(
f"\n✗ Coverage {coverage_percent:.2f}% is below required {fail_under}%"
)
retval = 1
if uncovered:
print(
"\nUncovered lines (explicitly reported by gcovr as coverable with 0 hits):"
)
for filename, lineno in sorted(uncovered):
print(f" {filename}:{lineno}")
else:
if total == 0:
print("\nNo coverable lines found.")
else:
print(f"\n✓ Code coverage check passed!")
return retval
def main() -> int: def main() -> int:
"""Main entry point""" """Main entry point"""
parser = create_argument_parser() parser = create_argument_parser()
@@ -223,42 +352,33 @@ def main() -> int:
os.chdir(root) os.chdir(root)
print(f"Current working directory: {root}") print(f"Current working directory: {root}")
covered, total, uncovered, skipped_noncoverable = check_commit_coverage( if args.path:
args.commit, root # Path mode: ignore commit, compute coverage for file/dir
) covered, total, uncovered = check_path_coverage(args.path, root)
# Print results with better formatting return report_coverage(
title = f" Coverage analysis results for commit {args.commit} " header=f"'{args.path}'",
separator = "=" * len(title) total=total,
print(f"\n{separator}\n{title}\n{separator}") covered=covered,
print(f"New coverable lines (per gcovr): {total}") uncovered=uncovered,
print(f"Covered lines: {covered}") fail_under=args.fail_under,
print(f"Uncovered lines: {len(uncovered)}") total_label="Coverable lines (per gcovr)",
print(f"Skipped non-coverable changed lines: {skipped_noncoverable}") )
retval = 0 else:
# Commit mode: default behavior
if total > 0: covered, total, uncovered, skipped_noncoverable = check_commit_coverage(
coverage_percent = (covered / total) * 100 args.commit, root
print(f"Coverage: {coverage_percent:.2f}%")
# Check if coverage meets minimum requirement
if coverage_percent < args.fail_under:
print(
f"\n✗ Coverage {coverage_percent:.2f}% is below required {args.fail_under}%"
)
retval = 1
if uncovered:
print(
"\nUncovered lines (explicitly reported by gcovr as coverable with 0 hits):"
) )
for filename, lineno in sorted(uncovered):
print(f" {filename}:{lineno}")
return retval return report_coverage(
header=f"commit {args.commit}",
print("\n✓ All new coverable code is covered!") total=total,
return retval covered=covered,
uncovered=uncovered,
fail_under=args.fail_under,
total_label="New coverable lines (per gcovr)",
skipped_noncoverable=skipped_noncoverable,
)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"Error: Command failed - {e}", file=sys.stderr) print(f"Error: Command failed - {e}", file=sys.stderr)