Skip to content

Commit 71c0eb2

Browse files
sbryngelsonclaude
andcommitted
Wrap gcda_dir in try/finally to prevent temp directory leak
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5603b0e commit 71c0eb2

File tree

1 file changed

+49
-50
lines changed

1 file changed

+49
-50
lines changed

toolchain/mfc/test/coverage.py

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -409,59 +409,58 @@ def build_coverage_cache( # pylint: disable=unused-argument,too-many-locals,too
409409
cons.print()
410410

411411
gcda_dir = tempfile.mkdtemp(prefix="mfc_gcov_")
412-
413-
# Phase 1: Run all tests in parallel via direct binary invocation.
414-
cons.print("[bold]Phase 1/2: Running tests...[/bold]")
415-
test_results: dict = {}
416-
all_failures: dict = {}
417-
with ThreadPoolExecutor(max_workers=n_jobs) as pool:
418-
futures = {
419-
pool.submit(_run_single_test_direct, info, gcda_dir, strip): info
420-
for info in test_infos
421-
}
422-
for i, future in enumerate(as_completed(futures)):
423-
uuid, test_gcda, failures = future.result()
424-
test_results[uuid] = test_gcda
425-
if failures:
426-
all_failures[uuid] = failures
412+
try:
413+
# Phase 1: Run all tests in parallel via direct binary invocation.
414+
cons.print("[bold]Phase 1/2: Running tests...[/bold]")
415+
test_results: dict = {}
416+
all_failures: dict = {}
417+
with ThreadPoolExecutor(max_workers=n_jobs) as pool:
418+
futures = {
419+
pool.submit(_run_single_test_direct, info, gcda_dir, strip): info
420+
for info in test_infos
421+
}
422+
for i, future in enumerate(as_completed(futures)):
423+
uuid, test_gcda, failures = future.result()
424+
test_results[uuid] = test_gcda
425+
if failures:
426+
all_failures[uuid] = failures
427+
if (i + 1) % 50 == 0 or (i + 1) == len(cases):
428+
cons.print(f" [{i+1:3d}/{len(cases):3d}] tests completed")
429+
430+
if all_failures:
431+
cons.print()
432+
cons.print(f"[bold yellow]Warning: {len(all_failures)} tests had target failures:[/bold yellow]")
433+
for uuid, fails in sorted(all_failures.items()):
434+
fail_str = ", ".join(f"{t}={rc}" for t, rc in fails)
435+
cons.print(f" [yellow]{uuid}[/yellow]: {fail_str}")
436+
437+
# Phase 2: Collect gcov coverage from each test's isolated .gcda directory.
438+
# For each test, copy its .gcda files into the build tree, run gcov only
439+
# on matching .gcno files (not all 414), then clean up. Targeting matching
440+
# .gcno files gives ~8x speedup over the full scan.
441+
cons.print()
442+
cons.print("[bold]Phase 2/2: Collecting coverage...[/bold]")
443+
cache: dict = {}
444+
for i, (uuid, test_gcda) in enumerate(sorted(test_results.items())):
445+
zero_gcda_files(root_dir)
446+
n_copied = _install_gcda_files(test_gcda, root_dir)
447+
448+
if n_copied == 0:
449+
coverage = set()
450+
else:
451+
# Only run gcov on .gcno files that have a matching .gcda installed.
452+
matching = _find_matching_gcno(root_dir)
453+
coverage = collect_coverage_for_test(
454+
matching or gcno_files, root_dir, gcov_bin
455+
)
456+
457+
cache[uuid] = sorted(coverage)
427458
if (i + 1) % 50 == 0 or (i + 1) == len(cases):
428-
cons.print(f" [{i+1:3d}/{len(cases):3d}] tests completed")
459+
cons.print(f" [{i+1:3d}/{len(cases):3d}] tests processed")
429460

430-
if all_failures:
431-
cons.print()
432-
cons.print(f"[bold yellow]Warning: {len(all_failures)} tests had target failures:[/bold yellow]")
433-
for uuid, fails in sorted(all_failures.items()):
434-
fail_str = ", ".join(f"{t}={rc}" for t, rc in fails)
435-
cons.print(f" [yellow]{uuid}[/yellow]: {fail_str}")
436-
437-
# Phase 2: Collect gcov coverage from each test's isolated .gcda directory.
438-
# For each test, copy its .gcda files into the build tree, run gcov only
439-
# on matching .gcno files (not all 414), then clean up. Targeting matching
440-
# .gcno files gives ~8x speedup over the full scan.
441-
cons.print()
442-
cons.print("[bold]Phase 2/2: Collecting coverage...[/bold]")
443-
cache: dict = {}
444-
for i, (uuid, test_gcda) in enumerate(sorted(test_results.items())):
445461
zero_gcda_files(root_dir)
446-
n_copied = _install_gcda_files(test_gcda, root_dir)
447-
448-
if n_copied == 0:
449-
coverage = set()
450-
else:
451-
# Only run gcov on .gcno files that have a matching .gcda installed.
452-
matching = _find_matching_gcno(root_dir)
453-
coverage = collect_coverage_for_test(
454-
matching or gcno_files, root_dir, gcov_bin
455-
)
456-
457-
cache[uuid] = sorted(coverage)
458-
if (i + 1) % 50 == 0 or (i + 1) == len(cases):
459-
cons.print(f" [{i+1:3d}/{len(cases):3d}] tests processed")
460-
461-
zero_gcda_files(root_dir)
462-
463-
# Clean up temp directory.
464-
shutil.rmtree(gcda_dir, ignore_errors=True)
462+
finally:
463+
shutil.rmtree(gcda_dir, ignore_errors=True)
465464

466465
# Sanity check: at least some tests should have non-empty coverage.
467466
tests_with_coverage = sum(1 for v in cache.values() if v)

0 commit comments

Comments
 (0)