Skip to content

Commit bf156f4

Browse files
committed
Fix print uncovered lines tool
1 parent f317b16 commit bf156f4

1 file changed

Lines changed: 82 additions & 26 deletions

File tree

tools/print_uncovered_lines.py

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,6 @@
3434

3535
DEFAULT_REPO = "kunitoki/yup"
3636

37-
DEFAULT_TARGETS = [
38-
"yup_GpuPipeline.cpp",
39-
"yup_GpuRenderPass.cpp",
40-
"yup_GpuCanvas.cpp",
41-
"yup_GpuBuffer.cpp",
42-
"yup_GpuTexture.cpp",
43-
"yup_Image.cpp",
44-
"yup_TypeErasedObject.h",
45-
"yup_Graphics.cpp",
46-
"yup_GpuPipelineCache.cpp",
47-
"yup_GpuFrame.cpp",
48-
]
49-
5037

5138
def get_github_token() -> str:
5239
"""Return the GitHub token from environment variables."""
@@ -183,14 +170,45 @@ def redirect_request(self, req, fp, code, msg, headers, newurl):
183170
sys.exit(1)
184171

185172

173+
def get_pr_changed_files(pr_number: int, repo: str, token: str) -> set[str]:
174+
"""Return the set of file paths changed in a PR.
175+
176+
Paths are relative to the repository root (e.g.
177+
``modules/yup_graphics/graphics/yup_GpuPipeline.cpp``).
178+
"""
179+
files: set[str] = set()
180+
page = 1
181+
182+
while True:
183+
url = (
184+
f"https://api.github.com/repos/{repo}/pulls/{pr_number}/files?per_page=100&page={page}"
185+
)
186+
data = github_api_request(url, token)
187+
188+
if not isinstance(data, list):
189+
break
190+
191+
for entry in data:
192+
filename = entry.get("filename", "")
193+
if filename:
194+
files.add(filename)
195+
196+
if len(data) < 100:
197+
break
198+
page += 1
199+
200+
return files
201+
202+
186203
def resolve_coverage_from_pr(
187204
pr_number: int,
188205
repo: str,
189206
workflow_name: str,
190-
) -> Path:
207+
) -> tuple[Path, set[str]]:
191208
"""Fetch the coverage artifact from a GitHub Actions PR run.
192209
193-
Returns the path to the extracted ``coverage_final.info`` file.
210+
Returns a ``(coverage_path, changed_files)`` tuple where *changed_files*
211+
is the set of file paths modified in the PR.
194212
"""
195213
token = get_github_token()
196214

@@ -292,7 +310,14 @@ def resolve_coverage_from_pr(
292310

293311
coverage_path = info_files[0]
294312
print(f"Using coverage file: {coverage_path}")
295-
return coverage_path
313+
314+
# ------------------------------------------------------------------
315+
# 6. Get the list of files changed in the PR
316+
# ------------------------------------------------------------------
317+
changed_files = get_pr_changed_files(pr_number, repo, token)
318+
print(f"PR #{pr_number} changed {len(changed_files)} file(s)")
319+
320+
return coverage_path, changed_files
296321

297322

298323
def parse_arguments() -> argparse.Namespace:
@@ -326,7 +351,8 @@ def parse_arguments() -> argparse.Namespace:
326351
parser.add_argument(
327352
"--all",
328353
action="store_true",
329-
help="Report every source file found in the coverage file.",
354+
help="Report every source file found in the coverage file. "
355+
"With --pr this bypasses both the PR-changed-files and targets filters.",
330356
)
331357
parser.add_argument(
332358
"--repo",
@@ -372,22 +398,47 @@ def parse_lcov_records(content: str) -> list[tuple[str, list[tuple[int, int]]]]:
372398
return records
373399

374400

375-
def should_report(source_file: str, targets: list[str], report_all: bool) -> bool:
376-
return report_all or any(target in source_file for target in targets)
401+
def should_report(
402+
source_file: str,
403+
targets: list[str],
404+
report_all: bool,
405+
changed_files: set[str] | None = None,
406+
) -> bool:
407+
"""Return True if *source_file* should appear in the coverage report."""
408+
if report_all:
409+
return True
410+
411+
if changed_files is not None:
412+
# Only report files that were changed in the PR.
413+
# SF paths in LCOV are absolute; PR file paths are repo-relative.
414+
# Match by checking if any PR path is a suffix of the SF path.
415+
if not any(source_file.endswith(f"/{f}") or source_file.endswith(f"\\{f}") for f in changed_files):
416+
return False
417+
418+
if targets:
419+
return any(target in source_file for target in targets)
420+
421+
# No targets filter active — show everything that passed earlier checks.
422+
return True
377423

378424

379425
def display_name(source_file: str, targets: list[str], report_all: bool) -> str:
380-
if report_all:
426+
if report_all or len(targets) == 0:
381427
return source_file
382428

383-
return next(target for target in targets if target in source_file)
429+
return next((target for target in targets if target in source_file), source_file)
384430

385431

386-
def print_uncovered_lines(coverage_file: Path, targets: list[str], report_all: bool) -> None:
432+
def print_uncovered_lines(
433+
coverage_file: Path,
434+
targets: list[str],
435+
report_all: bool,
436+
changed_files: set[str] | None = None,
437+
) -> None:
387438
content = coverage_file.read_text(encoding="utf-8")
388439

389440
for source_file, coverage_lines in parse_lcov_records(content):
390-
if not should_report(source_file, targets, report_all):
441+
if not should_report(source_file, targets, report_all, changed_files):
391442
continue
392443

393444
hit = [line_number for line_number, count in coverage_lines if count > 0]
@@ -404,14 +455,19 @@ def print_uncovered_lines(coverage_file: Path, targets: list[str], report_all: b
404455

405456
def main() -> None:
406457
args = parse_arguments()
407-
targets = args.targets if args.targets else DEFAULT_TARGETS
458+
changed_files: set[str] | None = None
408459

409460
if args.pr is not None:
410-
coverage_file = resolve_coverage_from_pr(args.pr, args.repo, args.workflow)
461+
coverage_file, changed_files = resolve_coverage_from_pr(
462+
args.pr, args.repo, args.workflow
463+
)
411464
else:
412465
coverage_file = args.coverage_file
466+
changed_files = None
467+
468+
targets = args.targets
413469

414-
print_uncovered_lines(coverage_file, targets, args.all)
470+
print_uncovered_lines(coverage_file, targets, args.all, changed_files)
415471

416472

417473
if __name__ == "__main__":

0 commit comments

Comments
 (0)