107 """Entry point for this script."""
108 args = parse_args()
109 repo_root = Path(__file__).resolve().parents[1]
110 src_dir = (repo_root / args.src_dir).resolve()
111 obj_dir = (repo_root / args.obj_dir).resolve()
112 output_dir = (repo_root / args.output_dir).resolve()
113 output_dir.mkdir(parents=True, exist_ok=True)
114
115 if not src_dir.is_dir():
116 raise SystemExit(f"[coverage-c] src directory missing: {src_dir}")
117 if not obj_dir.is_dir():
118 raise SystemExit(f"[coverage-c] obj directory missing: {obj_dir}")
119
120 for old in output_dir.glob("*.gcov"):
121 old.unlink()
122
123 src_files = sorted(src_dir.glob("*.c"))
124 if not src_files:
125 raise SystemExit(f"[coverage-c] no source files found in {src_dir}")
126
127 run_gcov(src_files, obj_dir, repo_root, output_dir)
128
129 by_source: dict[Path, tuple[int, int]] = {}
130 for gcov_path in sorted(output_dir.glob("*.gcov")):
131 source, covered, total = parse_gcov_file(gcov_path, repo_root)
132 if source is None:
133 continue
134 if source.parent != src_dir:
135 continue
136 prev_cov, prev_total = by_source.get(source, (0, 0))
137 by_source[source] = (prev_cov + covered, prev_total + total)
138
139 print("[coverage-c] per-file line coverage")
140 print("[coverage-c] -----------------------------------------------")
141
142 total_cov = 0
143 total_exec = 0
144 missing = []
145 for src in src_files:
146 covered, executable = by_source.get(src, (0, 0))
147 if executable == 0:
148 missing.append(src)
149 percent = 0.0
150 else:
151 percent = (100.0 * covered) / executable
152 total_cov += covered
153 total_exec += executable
154 rel = src.relative_to(repo_root)
155 print(f"[coverage-c] {rel}: {covered}/{executable} ({percent:.2f}%)")
156
157 overall = 0.0 if total_exec == 0 else (100.0 * total_cov) / total_exec
158 print("[coverage-c] -----------------------------------------------")
159 print(f"[coverage-c] weighted total: {total_cov}/{total_exec} ({overall:.2f}%)")
160 print(f"[coverage-c] minimum required: {args.min_line:.2f}%")
161
162 summary_path = output_dir / "summary.txt"
163 summary_path.write_text(
164 "\n".join(
165 [
166 f"weighted_total={overall:.4f}",
167 f"covered_lines={total_cov}",
168 f"executable_lines={total_exec}",
169 f"minimum_required={args.min_line:.4f}",
170 ]
171 )
172 + "\n",
173 encoding="utf-8",
174 )
175
176 if missing:
177 print("[coverage-c] WARNING: missing or zero-coverage gcov data for:", file=sys.stderr)
178 for src in missing:
179 print(f"[coverage-c] - {src.relative_to(repo_root)}", file=sys.stderr)
180
181 if overall < args.min_line:
182 print(
183 f"[coverage-c] FAIL: coverage {overall:.2f}% is below required {args.min_line:.2f}%.",
184 file=sys.stderr,
185 )
186 return 2
187
188 return 0
189
190
int main(int argc, char **argv)