pluginbench 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. pluginbench/__init__.py +3 -0
  2. pluginbench/__main__.py +3 -0
  3. pluginbench/assessment-charts.html +790 -0
  4. pluginbench/assessment.py +316 -0
  5. pluginbench/assessment_charts.py +1225 -0
  6. pluginbench/assessment_metrics.py +1956 -0
  7. pluginbench/assessment_models.py +127 -0
  8. pluginbench/baselines.py +792 -0
  9. pluginbench/claude.py +374 -0
  10. pluginbench/cli.py +672 -0
  11. pluginbench/code_diff.py +207 -0
  12. pluginbench/codex.py +451 -0
  13. pluginbench/codex_events.py +585 -0
  14. pluginbench/collectors.py +591 -0
  15. pluginbench/compare.py +927 -0
  16. pluginbench/components.py +129 -0
  17. pluginbench/coverage_report.py +610 -0
  18. pluginbench/datasets.py +207 -0
  19. pluginbench/evaluation.example.yaml +45 -0
  20. pluginbench/evaluation.py +1651 -0
  21. pluginbench/evaluation_report.py +1004 -0
  22. pluginbench/fallow_parse.mjs +101 -0
  23. pluginbench/harbor_backend.py +2946 -0
  24. pluginbench/isolation.py +314 -0
  25. pluginbench/judge.py +683 -0
  26. pluginbench/language_analysis.py +915 -0
  27. pluginbench/ledger.py +217 -0
  28. pluginbench/measurement_profiles.py +616 -0
  29. pluginbench/native_treatment_runtime.js +221 -0
  30. pluginbench/native_treatments.py +155 -0
  31. pluginbench/observer_data/go-analysis-v1/go.mod +10 -0
  32. pluginbench/observer_data/go-analysis-v1/go.sum +10 -0
  33. pluginbench/observer_data/go-analysis-v1/main.go +64 -0
  34. pluginbench/observer_data/oxc-parser-0.126.0/package-lock.json +449 -0
  35. pluginbench/observer_data/oxc-parser-0.126.0/package.json +5 -0
  36. pluginbench/parallel.py +101 -0
  37. pluginbench/processes.py +100 -0
  38. pluginbench/remeasure.py +992 -0
  39. pluginbench/run_lock.py +227 -0
  40. pluginbench/run_status.py +518 -0
  41. pluginbench/study.py +1858 -0
  42. pluginbench/study_review.py +924 -0
  43. pluginbench/submissions.py +486 -0
  44. pluginbench/task_sources.py +403 -0
  45. pluginbench/treatments.py +388 -0
  46. pluginbench/trial_containers.py +94 -0
  47. pluginbench/vendor/ECHARTS-LICENSE +222 -0
  48. pluginbench/vendor/ECHARTS-NOTICE +5 -0
  49. pluginbench/vendor/ZRENDER-LICENSE +29 -0
  50. pluginbench/vendor/echarts.min.js +48 -0
  51. pluginbench-0.5.0.dist-info/METADATA +294 -0
  52. pluginbench-0.5.0.dist-info/RECORD +58 -0
  53. pluginbench-0.5.0.dist-info/WHEEL +4 -0
  54. pluginbench-0.5.0.dist-info/entry_points.txt +2 -0
  55. pluginbench-0.5.0.dist-info/licenses/LICENSE +201 -0
  56. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-LICENSE +222 -0
  57. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-NOTICE +5 -0
  58. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ZRENDER-LICENSE +29 -0
@@ -0,0 +1,610 @@
1
+ """Normalize coverage formats into independent, source-bound coordinate populations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ import fnmatch
7
+ import base64
8
+ import json
9
+ from collections.abc import Iterable
10
+ from pathlib import Path, PurePosixPath
11
+ import re
12
+
13
+ from pluginbench.assessment_metrics import inspect_snapshot
14
+
15
+
16
+ class CoveragePopulationUnavailable(RuntimeError):
17
+ """The report contains no measured sources or usable coordinate populations."""
18
+
19
+
20
+ def _integer(value: str) -> int:
21
+ if not re.fullmatch(r"\d+", value):
22
+ raise ValueError(f"Coverage count must be a nonnegative integer: {value!r}")
23
+ return int(value)
24
+
25
+
26
+ def _source_path(value: str, files: set[str], scope: list[str], workdir: str) -> str:
27
+ path = PurePosixPath(value)
28
+ if path.is_absolute():
29
+ try:
30
+ path = path.relative_to(PurePosixPath(workdir))
31
+ except ValueError as exc:
32
+ raise ValueError(f"Coverage source escapes the measured repository: {value}") from exc
33
+ if ".." in path.parts or path.as_posix() not in files:
34
+ raise ValueError(f"Coverage source is absent from the frozen snapshot: {value}")
35
+ relative = path.as_posix()
36
+ if not any(fnmatch.fnmatchcase(relative, pattern) for pattern in scope):
37
+ raise ValueError(f"Coverage source escapes declared scope: {relative}")
38
+ return relative
39
+
40
+
41
+ @dataclass
42
+ class _Record:
43
+ path: str
44
+ line_count: int
45
+ definitions: dict[str, list[int]] = field(default_factory=dict)
46
+ hits: dict[str, list[int]] = field(default_factory=dict)
47
+ branches: dict[str, int] = field(default_factory=dict)
48
+ summaries: dict[str, int] = field(default_factory=dict)
49
+ lines: dict[int, int] = field(default_factory=dict)
50
+
51
+ def line(self, value: str) -> int:
52
+ line = _integer(value)
53
+ if not 1 <= line <= self.line_count:
54
+ raise ValueError(f"LCOV coordinate escapes source line population: {self.path}:{line}")
55
+ return line
56
+
57
+ def function(self, key: str, value: str) -> None:
58
+ fields = value.split(",", 1)
59
+ target = self.definitions if key == "FN" else self.hits
60
+ if len(fields) != 2 or not fields[1]:
61
+ raise ValueError(f"Invalid or duplicate LCOV function: {value}")
62
+ coordinate = self.line(fields[0]) if key == "FN" else _integer(fields[0])
63
+ values = target.setdefault(fields[1], [])
64
+ if key == "FN" and coordinate in values:
65
+ raise ValueError(f"Duplicate LCOV function coordinate: {value}")
66
+ values.append(coordinate)
67
+
68
+ def function_coverage(self) -> dict[str, int] | None:
69
+ result = {}
70
+ for name, lines in self.definitions.items():
71
+ covered = {count > 0 for count in self.hits[name]}
72
+ # FNDA carries no source line: mixed hits cannot be paired safely.
73
+ if len(covered) > 1:
74
+ return None
75
+ result.update({f"{line}:{name}": int(True in covered) for line in lines})
76
+ return result
77
+
78
+ def branch(self, value: str) -> None:
79
+ fields = value.split(",")
80
+ if len(fields) != 4:
81
+ raise ValueError(f"Invalid LCOV branch: {value}")
82
+ coordinate = ":".join(
83
+ [str(self.line(fields[0])), str(_integer(fields[1])), str(_integer(fields[2]))]
84
+ )
85
+ if coordinate in self.branches:
86
+ raise ValueError(f"Duplicate LCOV branch: {value}")
87
+ self.branches[coordinate] = 0 if fields[3] == "-" else _integer(fields[3])
88
+
89
+ def line_hits(self, value: str) -> None:
90
+ fields = value.split(",")
91
+ if len(fields) not in {2, 3}:
92
+ raise ValueError(f"Invalid LCOV line: {value}")
93
+ line = self.line(fields[0])
94
+ if line in self.lines:
95
+ raise ValueError(f"Duplicate LCOV line: {value}")
96
+ self.lines[line] = _integer(fields[1])
97
+
98
+ def add(self, key: str, value: str) -> None:
99
+ if key in {"FN", "FNDA"}:
100
+ self.function(key, value)
101
+ elif key == "BRDA":
102
+ self.branch(value)
103
+ elif key in {"FNF", "FNH", "BRF", "BRH", "LF", "LH"}:
104
+ if key in self.summaries:
105
+ raise ValueError(f"Duplicate LCOV summary: {key}")
106
+ self.summaries[key] = _integer(value)
107
+ elif key == "DA":
108
+ self.line_hits(value)
109
+ elif key != "VER":
110
+ raise ValueError(f"Unsupported LCOV record: {key}")
111
+
112
+ def validate(self) -> None:
113
+ if self.definitions.keys() != self.hits.keys() or any(
114
+ len(lines) != len(self.hits[name]) for name, lines in self.definitions.items()
115
+ ):
116
+ raise ValueError(f"LCOV function definitions and hit counts differ: {self.path}")
117
+ observed = {
118
+ "FNF": sum(len(lines) for lines in self.definitions.values()),
119
+ "FNH": sum(count > 0 for counts in self.hits.values() for count in counts),
120
+ "BRF": len(self.branches),
121
+ "BRH": sum(count > 0 for count in self.branches.values()),
122
+ "LF": len(self.lines),
123
+ "LH": sum(count > 0 for count in self.lines.values()),
124
+ }
125
+ if any(value != observed[key] for key, value in self.summaries.items()):
126
+ raise ValueError(f"LCOV summaries disagree with coordinate populations: {self.path}")
127
+
128
+
129
+ def _record(
130
+ rows: list[str], snapshot: Path, files: set[str], scope: list[str], workdir: str
131
+ ) -> _Record:
132
+ if not rows or not rows[0].startswith("SF:"):
133
+ raise ValueError("LCOV record must begin with a source path")
134
+ path = _source_path(rows[0][3:], files, scope, workdir)
135
+ result = _Record(path, len((snapshot / path).read_text().splitlines()))
136
+ for raw in rows[1:]:
137
+ key, separator, value = raw.partition(":")
138
+ if not separator:
139
+ raise ValueError(f"Malformed LCOV record: {raw}")
140
+ result.add(key, value)
141
+ result.validate()
142
+ return result
143
+
144
+
145
+ def _records(
146
+ report: Path, snapshot: Path, files: set[str], scope: list[str], workdir: str
147
+ ) -> list[_Record]:
148
+ records = []
149
+ seen: set[str] = set()
150
+ rows: list[str] = []
151
+ for raw in report.read_text(encoding="utf-8").splitlines():
152
+ if not raw or raw.startswith("TN:"):
153
+ continue
154
+ if raw != "end_of_record":
155
+ rows.append(raw)
156
+ continue
157
+ record = _record(rows, snapshot, files, scope, workdir)
158
+ if record.path in seen:
159
+ raise ValueError(f"Coverage source is duplicated: {record.path}")
160
+ seen.add(record.path)
161
+ records.append(record)
162
+ rows = []
163
+ if rows:
164
+ raise ValueError("Coverage source record was not terminated")
165
+ if not records:
166
+ raise CoveragePopulationUnavailable("LCOV report contains no measured sources")
167
+ return records
168
+
169
+
170
+ def parse_lcov(
171
+ report: Path,
172
+ *,
173
+ snapshot: Path,
174
+ source_scope: list[str],
175
+ test_scope: list[str],
176
+ workdir: str,
177
+ ) -> dict[str, object]:
178
+ """Keep available populations independently; missing kinds stay unavailable."""
179
+ inventory = inspect_snapshot(snapshot)["files"]
180
+ assert isinstance(inventory, dict)
181
+ records = _records(report, snapshot, set(inventory), source_scope, workdir)
182
+ result = _metadata("lcov", records, source_scope, test_scope)
183
+ ambiguous = sorted(record.path for record in records if record.function_coverage() is None)
184
+ if ambiguous:
185
+ result["unavailable_populations"] = {
186
+ "functions": {
187
+ "files": ambiguous,
188
+ "reason": "Repeated LCOV function names have mixed hits; FNDA has no source line to identify which function was covered",
189
+ }
190
+ }
191
+ for kind, summaries in (
192
+ ("functions", {"FNF", "FNH"}),
193
+ ("branches", {"BRF", "BRH"}),
194
+ ("lines", {"LF", "LH"}),
195
+ ):
196
+ measured = [
197
+ record
198
+ for record in records
199
+ if (summaries <= record.summaries.keys() or (kind == "lines" and record.lines))
200
+ and not (kind == "functions" and record.path in ambiguous)
201
+ ]
202
+ if not measured:
203
+ continue
204
+ total: set[str] = set()
205
+ covered: set[str] = set()
206
+ for record in measured:
207
+ if kind == "functions":
208
+ hits = record.function_coverage()
209
+ assert hits is not None
210
+ else:
211
+ hits = (
212
+ record.branches
213
+ if kind == "branches"
214
+ else {str(line): count for line, count in record.lines.items()}
215
+ )
216
+ total.update(f"{record.path}:{coordinate}" for coordinate in hits)
217
+ covered.update(
218
+ f"{record.path}:{coordinate}" for coordinate, count in hits.items() if count
219
+ )
220
+ result[kind] = _population(total, covered, [record.path for record in measured], kind)
221
+ if not any(kind in result for kind in ("lines", "branches", "functions")):
222
+ raise CoveragePopulationUnavailable("LCOV contains no explicit coverage populations")
223
+ per_test = _lcov_per_test(
224
+ report.with_name(report.name + ".per-test.json"),
225
+ records=records,
226
+ inventory=inventory,
227
+ source_scope=source_scope,
228
+ workdir=workdir,
229
+ )
230
+ if per_test:
231
+ result["per_test_numbits"] = per_test
232
+ return result
233
+
234
+
235
+ def _lcov_per_test(
236
+ sidecar: Path,
237
+ *,
238
+ records: list[_Record],
239
+ inventory: dict[str, object],
240
+ source_scope: list[str],
241
+ workdir: str,
242
+ ) -> dict[str, dict[str, str]]:
243
+ """Validate file-to-context numbits against LCOV's executed line population."""
244
+ if not sidecar.exists():
245
+ return {}
246
+ data = _json_object(json.loads(sidecar.read_text(), object_pairs_hook=_json_pairs), "sidecar")
247
+ raw_files = _json_object(data.get("per_test_numbits"), "LCOV per-test numbits")
248
+ measured = {record.path: record for record in records}
249
+ seen: set[str] = set()
250
+ per_test: dict[str, dict[str, str]] = {}
251
+ for raw_path, contexts in raw_files.items():
252
+ path = _source_path(raw_path, set(inventory), source_scope, workdir)
253
+ if path not in measured:
254
+ raise ValueError(f"LCOV per-test coverage names an unmeasured source: {path}")
255
+ if path in seen:
256
+ raise ValueError(f"LCOV per-test source is duplicated: {path}")
257
+ seen.add(path)
258
+ covered = {line for line, count in measured[path].lines.items() if count}
259
+ for name, encoded in _json_object(contexts, "LCOV per-test contexts").items():
260
+ if not isinstance(encoded, str):
261
+ raise ValueError("LCOV per-test numbits must be base64 strings")
262
+ lines = numbits_to_lines(encoded)
263
+ if not lines <= covered:
264
+ raise ValueError("LCOV per-test coverage names an unexecuted or unmeasured line")
265
+ if name:
266
+ per_test.setdefault(name, {})[path] = lines_to_numbits(lines)
267
+ return dict(sorted(per_test.items()))
268
+
269
+
270
+ def _population(
271
+ total: set[str], covered: set[str], files: list[str], kind: str
272
+ ) -> dict[str, object]:
273
+ return {
274
+ "total": sorted(total),
275
+ "covered": sorted(covered),
276
+ "files_measured": sorted(files),
277
+ "unit": {
278
+ "lines": "line",
279
+ "branches": "branch",
280
+ "functions": "function",
281
+ "statements": "statement",
282
+ }[kind],
283
+ }
284
+
285
+
286
+ def _metadata(
287
+ format: str, records: list[_Record], source_scope: list[str], test_scope: list[str]
288
+ ) -> dict[str, object]:
289
+ return {
290
+ "files_measured": sorted(record.path for record in records),
291
+ "source_scope": source_scope,
292
+ "test_scope": test_scope,
293
+ "format": format,
294
+ }
295
+
296
+
297
+ def _json_object(value: object, label: str) -> dict[str, object]:
298
+ if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
299
+ raise ValueError(f"{label} must be an object")
300
+ return dict(value)
301
+
302
+
303
+ def _json_pairs(pairs: list[tuple[str, object]]) -> dict[str, object]:
304
+ result: dict[str, object] = {}
305
+ for key, value in pairs:
306
+ if key in result:
307
+ raise ValueError(f"Duplicate coverage JSON key: {key}")
308
+ result[key] = value
309
+ return result
310
+
311
+
312
+ def _json_count(value: object) -> int:
313
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
314
+ raise ValueError(f"Coverage count must be a nonnegative integer: {value!r}")
315
+ return value
316
+
317
+
318
+ def _python_lines(value: object, record: _Record) -> set[int]:
319
+ if not isinstance(value, list):
320
+ raise ValueError("Python coverage lines must be a list")
321
+ lines = [record.line(str(_json_count(line))) for line in value]
322
+ if len(lines) != len(set(lines)):
323
+ raise ValueError("Duplicate Python coverage line")
324
+ return set(lines)
325
+
326
+
327
+ def _python_branches(value: object, record: _Record) -> set[str]:
328
+ if not isinstance(value, list):
329
+ raise ValueError("Python coverage branches must be a list")
330
+ branches: set[str] = set()
331
+ for arc in value:
332
+ if not isinstance(arc, list) or len(arc) != 2:
333
+ raise ValueError("Python coverage branch must contain two line coordinates")
334
+ source = record.line(str(_json_count(arc[0])))
335
+ destination = arc[1]
336
+ if isinstance(destination, bool) or not isinstance(destination, int) or destination == 0:
337
+ raise ValueError("Invalid Python coverage branch destination")
338
+ record.line(str(abs(destination)))
339
+ coordinate = f"{source}:{destination}"
340
+ if coordinate in branches:
341
+ raise ValueError("Duplicate Python coverage branch")
342
+ branches.add(coordinate)
343
+ return branches
344
+
345
+
346
+ def _python_file(
347
+ value: object, record: _Record, branch_coverage: bool
348
+ ) -> dict[str, tuple[set[str], set[str]]]:
349
+ data = _json_object(value, "Python coverage file")
350
+ summary = _json_object(data.get("summary"), "Python coverage summary")
351
+ raw_executed = data.get("executed_lines")
352
+ if isinstance(raw_executed, list):
353
+ # coverage.py reports line 0, the module entry, as executed for a file without statements.
354
+ raw_executed = [line for line in raw_executed if line != 0]
355
+ executed = _python_lines(raw_executed, record)
356
+ missing = _python_lines(data.get("missing_lines"), record)
357
+ excluded = _python_lines(data.get("excluded_lines"), record)
358
+ if "statement_lines" in data:
359
+ statements = _python_lines(data["statement_lines"], record)
360
+ if missing != statements - executed or executed & missing:
361
+ raise ValueError(
362
+ "Python statement coordinates do not partition into covered and missing"
363
+ )
364
+ executed &= statements
365
+ if executed & missing or (executed | missing) & excluded:
366
+ raise ValueError("Python coverage line populations overlap")
367
+ expected = {
368
+ "num_statements": len(executed | missing),
369
+ "covered_lines": len(executed),
370
+ "missing_lines": len(missing),
371
+ "excluded_lines": len(excluded),
372
+ }
373
+ result = {
374
+ "lines": ({str(line) for line in executed | missing}, {str(line) for line in executed})
375
+ }
376
+ if branch_coverage:
377
+ covered = _python_branches(data.get("executed_branches"), record)
378
+ uncovered = _python_branches(data.get("missing_branches"), record)
379
+ if covered & uncovered:
380
+ raise ValueError("Python coverage branch populations overlap")
381
+ result["branches"] = (covered | uncovered, covered)
382
+ expected.update(
383
+ num_branches=len(covered | uncovered),
384
+ covered_branches=len(covered),
385
+ missing_branches=len(uncovered),
386
+ )
387
+ elif "executed_branches" in data or "missing_branches" in data:
388
+ raise ValueError("Python branch population contradicts report metadata")
389
+ if any(_json_count(summary.get(key)) != count for key, count in expected.items()):
390
+ raise ValueError("Python coverage summaries disagree with coordinate populations")
391
+ return result
392
+
393
+
394
+ def parse_python_coverage(
395
+ report: Path, *, snapshot: Path, source_scope: list[str], test_scope: list[str], workdir: str
396
+ ) -> dict[str, object]:
397
+ """Read coverage.py JSON formats 2/3; excluded lines are outside the denominator."""
398
+ data = _json_object(json.loads(report.read_text(), object_pairs_hook=_json_pairs), "coverage")
399
+ meta = _json_object(data.get("meta"), "Python coverage metadata")
400
+ if _json_count(meta.get("format")) not in {2, 3}:
401
+ raise ValueError("Unsupported coverage.py JSON format")
402
+ if not isinstance(meta.get("branch_coverage"), bool) or not isinstance(
403
+ meta.get("version"), str
404
+ ):
405
+ raise ValueError("Python coverage metadata lacks branch mode or tool version")
406
+ branch_coverage = meta["branch_coverage"]
407
+ assert isinstance(branch_coverage, bool)
408
+ statement_source = meta.get("statement_lines_source")
409
+ if statement_source not in (None, "coverage.Coverage.analysis2"):
410
+ raise ValueError("Unsupported Python statement-coordinate source")
411
+ inventory = inspect_snapshot(snapshot)["files"]
412
+ assert isinstance(inventory, dict)
413
+ raw_files = _json_object(data.get("files"), "Python coverage files")
414
+ if not raw_files:
415
+ raise CoveragePopulationUnavailable("Python report contains no measured sources")
416
+ records: list[_Record] = []
417
+ populations: dict[str, tuple[set[str], set[str]]] = {}
418
+ executed_by_path: dict[str, tuple[set[str], set[str], int]] = {}
419
+ for raw_path, raw_file in raw_files.items():
420
+ if ("statement_lines" in _json_object(raw_file, "Python coverage file")) != (
421
+ statement_source is not None
422
+ ):
423
+ raise ValueError("Python statement-coordinate source differs from file metadata")
424
+ path = _source_path(raw_path, set(inventory), source_scope, workdir)
425
+ if not path.endswith(".py"):
426
+ raise ValueError(f"Python coverage source is not Python: {path}")
427
+ if path in {record.path for record in records}:
428
+ raise ValueError(f"Python coverage source is duplicated: {path}")
429
+ record = _Record(path, len((snapshot / path).read_text().splitlines()))
430
+ records.append(record)
431
+ file_populations = _python_file(raw_file, record, branch_coverage)
432
+ for kind, (total, covered) in file_populations.items():
433
+ aggregate = populations.setdefault(kind, (set(), set()))
434
+ aggregate[0].update(f"{path}:{coordinate}" for coordinate in total)
435
+ aggregate[1].update(f"{path}:{coordinate}" for coordinate in covered)
436
+ executed_by_path[path] = (*file_populations["lines"], record.line_count)
437
+ result = _metadata("coverage-py-json", records, source_scope, test_scope)
438
+ result["tool_version"] = meta["version"]
439
+ for kind, (total, covered) in populations.items():
440
+ result[kind] = _population(total, covered, [record.path for record in records], kind)
441
+ per_test = _python_per_test(
442
+ data.get("per_test_numbits"), inventory, source_scope, workdir, executed_by_path
443
+ )
444
+ if per_test: # contexts without test names leave per-test coverage unknown
445
+ result["per_test_numbits"] = per_test
446
+ return result
447
+
448
+
449
+ def lines_to_numbits(lines: Iterable[int]) -> str:
450
+ """coverage.py numbits layout (bit n = line n), base64-encoded."""
451
+ values = sorted(set(lines))
452
+ bits = bytearray(values[-1] // 8 + 1 if values else 0)
453
+ for line in values:
454
+ bits[line // 8] |= 1 << (line % 8)
455
+ return base64.b64encode(bytes(bits)).decode()
456
+
457
+
458
+ def numbits_to_lines(value: str) -> set[int]:
459
+ bits = base64.b64decode(value, validate=True)
460
+ return {
461
+ index * 8 + bit for index, byte in enumerate(bits) for bit in range(8) if byte >> bit & 1
462
+ }
463
+
464
+
465
+ def _python_per_test(
466
+ value: object,
467
+ inventory: dict[str, object],
468
+ source_scope: list[str],
469
+ workdir: str,
470
+ executed_by_path: dict[str, tuple[set[str], set[str], int]],
471
+ ) -> dict[str, dict[str, str]]:
472
+ """Per-test executed lines from dynamic contexts, kept as compact numbits per file."""
473
+ if value is None:
474
+ return {}
475
+ per_test: dict[str, dict[str, str]] = {}
476
+ for raw_path, contexts in _json_object(value, "Python per-test numbits").items():
477
+ path = _source_path(raw_path, set(inventory), source_scope, workdir)
478
+ if path not in executed_by_path:
479
+ raise ValueError(f"Python per-test coverage names an unmeasured source: {path}")
480
+ total, covered, line_count = executed_by_path[path]
481
+ executed = {int(line) for line in covered}
482
+ missing = {int(line) for line in total} - executed
483
+ for name, encoded in _json_object(contexts, "Python per-test contexts").items():
484
+ if not isinstance(encoded, str):
485
+ raise ValueError("Python per-test numbits must be base64 strings")
486
+ lines = numbits_to_lines(encoded) - {0} # coverage.py's module-entry line
487
+ # Traced non-statement lines (decorators, docstrings) stay outside the population;
488
+ # a statement the report calls missing, or a line outside the file, is an error.
489
+ if lines & missing or any(not 1 <= line <= line_count for line in lines):
490
+ raise ValueError("Python per-test coverage names an unexecuted line")
491
+ statements = lines & executed
492
+ if name and statements:
493
+ per_test.setdefault(name, {})[path] = lines_to_numbits(statements)
494
+ return dict(sorted(per_test.items()))
495
+
496
+
497
+ def _go_modules(snapshot: Path, files: set[str]) -> dict[str, list[PurePosixPath]]:
498
+ modules: dict[str, list[PurePosixPath]] = {}
499
+ for name in files:
500
+ if PurePosixPath(name).name != "go.mod":
501
+ continue
502
+ declarations = re.findall(
503
+ r'^\s*module\s+(?:"([^"\n]+)"|([^\s]+))\s*(?://[^\n]*)?$',
504
+ (snapshot / name).read_text(),
505
+ re.M,
506
+ )
507
+ if len(declarations) != 1:
508
+ raise ValueError(f"Cannot establish frozen Go module identity: {name}")
509
+ module = declarations[0][0] or declarations[0][1]
510
+ modules.setdefault(module, []).append(PurePosixPath(name).parent)
511
+ return modules
512
+
513
+
514
+ def _go_source(
515
+ raw_path: str,
516
+ *,
517
+ files: set[str],
518
+ modules: dict[str, list[PurePosixPath]],
519
+ source_scope: list[str],
520
+ workdir: str,
521
+ ) -> str:
522
+ if raw_path in files or raw_path.startswith(("/", "./", "../")):
523
+ return _source_path(raw_path, files, source_scope, workdir)
524
+ matches = [module for module in modules if raw_path.startswith(module + "/")]
525
+ if not matches:
526
+ raise ValueError(f"Go source cannot be mapped to a frozen module: {raw_path}")
527
+ module = max(matches, key=len)
528
+ directories = modules[module]
529
+ if len(directories) != 1:
530
+ raise ValueError(f"Go source cannot be mapped to one frozen module: {raw_path}")
531
+ path = (directories[0] / raw_path[len(module) + 1 :]).as_posix()
532
+ return _source_path(path, files, source_scope, workdir)
533
+
534
+
535
+ def parse_go_coverage(
536
+ report: Path, *, snapshot: Path, source_scope: list[str], test_scope: list[str], workdir: str
537
+ ) -> dict[str, object]:
538
+ """Keep basic-block coordinates and numStmt weights; Go measures statements."""
539
+ rows = report.read_text().splitlines()
540
+ if not rows or rows[0] not in {"mode: set", "mode: count", "mode: atomic"}:
541
+ raise ValueError("Invalid Go coverprofile mode")
542
+ inventory = inspect_snapshot(snapshot)["files"]
543
+ assert isinstance(inventory, dict)
544
+ files = set(inventory)
545
+ modules = _go_modules(snapshot, files)
546
+ source_lines: dict[str, list[str]] = {}
547
+ records: dict[str, _Record] = {}
548
+ weights: dict[str, int] = {}
549
+ covered: set[str] = set()
550
+ for row in rows[1:]:
551
+ match = re.fullmatch(r"(.+):([0-9]+)\.([0-9]+),([0-9]+)\.([0-9]+) ([0-9]+) ([0-9]+)", row)
552
+ if not match:
553
+ raise ValueError(f"Malformed Go coverprofile block: {row}")
554
+ raw_path, *values = match.groups()
555
+ path = _go_source(
556
+ raw_path, files=files, modules=modules, source_scope=source_scope, workdir=workdir
557
+ )
558
+ if not path.endswith(".go"):
559
+ raise ValueError(f"Go coverage source is not Go: {path}")
560
+ if path not in records:
561
+ source_lines[path] = (snapshot / path).read_text().splitlines()
562
+ records[path] = _Record(path, len(source_lines[path]))
563
+ lines = source_lines[path]
564
+ record = records[path]
565
+ start, start_col, end, end_col, statements, hits = map(_integer, values)
566
+ record.line(str(start))
567
+ record.line(str(end))
568
+ if not (
569
+ 1 <= start_col <= len(lines[start - 1].encode()) + 1
570
+ and 1 <= end_col <= len(lines[end - 1].encode()) + 1
571
+ and (start, start_col) < (end, end_col)
572
+ ):
573
+ raise ValueError(f"Go block escapes source span: {row}")
574
+ if rows[0] == "mode: set" and hits not in {0, 1}:
575
+ raise ValueError("Go set-mode hit count must be zero or one")
576
+ coordinate = f"{path}:{start}.{start_col},{end}.{end_col}"
577
+ if coordinate in weights:
578
+ raise ValueError(f"Duplicate Go coverage block: {coordinate}")
579
+ weights[coordinate] = statements
580
+ if hits:
581
+ covered.add(coordinate)
582
+ if not records:
583
+ raise CoveragePopulationUnavailable("Go report contains no measured source blocks")
584
+ result = _metadata("go-coverprofile", list(records.values()), source_scope, test_scope)
585
+ result["mode"] = rows[0][6:]
586
+ population = _population(set(weights), covered, list(records), "statements")
587
+ population["weights"] = weights
588
+ result["statements"] = population
589
+ return result
590
+
591
+
592
+ def parse_coverage(
593
+ format: str,
594
+ report: Path,
595
+ *,
596
+ snapshot: Path,
597
+ source_scope: list[str],
598
+ test_scope: list[str],
599
+ workdir: str,
600
+ ) -> dict[str, object]:
601
+ parsers = {
602
+ "lcov": parse_lcov,
603
+ "coverage-py-json": parse_python_coverage,
604
+ "go-coverprofile": parse_go_coverage,
605
+ }
606
+ if format not in parsers:
607
+ raise ValueError(f"Unsupported coverage format: {format}")
608
+ return parsers[format](
609
+ report, snapshot=snapshot, source_scope=source_scope, test_scope=test_scope, workdir=workdir
610
+ )