@biffo/cli 0.296.9 → 0.296.11
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.
|
@@ -418,16 +418,47 @@ def main() -> int:
|
|
|
418
418
|
file=sys.stderr,
|
|
419
419
|
)
|
|
420
420
|
|
|
421
|
+
reports = [json.loads(p.read_text()) for p in present]
|
|
422
|
+
coverage = merge_coverage(reports)
|
|
423
|
+
file_count = len(coverage.get("files", {}))
|
|
424
|
+
|
|
421
425
|
# Restated in every summary line below (not just the warning above) so the
|
|
422
426
|
# denominator survives even if the warning scrolls past unread: a reader
|
|
423
427
|
# who sees only the final line still learns how much of the requested
|
|
424
|
-
# picture this result is actually over.
|
|
425
|
-
|
|
428
|
+
# picture this result is actually over. The file count is part of that
|
|
429
|
+
# denominator, not just the artefact count — see the fail-closed check
|
|
430
|
+
# immediately below for why (#1657).
|
|
431
|
+
coverage_note = (
|
|
432
|
+
f"{len(present)}/{len(coverage_paths)} coverage artefact(s), {file_count} file(s)"
|
|
433
|
+
)
|
|
426
434
|
if absent:
|
|
427
435
|
coverage_note += f" — MISSING: {', '.join(str(p) for p in absent)}"
|
|
428
436
|
|
|
429
|
-
|
|
430
|
-
|
|
437
|
+
if file_count == 0:
|
|
438
|
+
# #1657: an artefact can be present and valid JSON while still
|
|
439
|
+
# describing NOTHING — `{"files": {}}`, or `files` absent entirely.
|
|
440
|
+
# That reads identically to "everything is covered" to the logic
|
|
441
|
+
# below (found == [], keys == [], nothing NEW), so --check would
|
|
442
|
+
# exit 0 over a report that examined zero files: a fail-open in the
|
|
443
|
+
# gate that exists specifically to catch fail-opens. Every other
|
|
444
|
+
# soft-open path here (no baseline, a missing --coverage file)
|
|
445
|
+
# announces itself loudly and still proceeds; this one cannot
|
|
446
|
+
# proceed at all, because there is no denominator left to reason
|
|
447
|
+
# over. Fail closed rather than fail open: refuse both --write
|
|
448
|
+
# (never baseline a measurement of nothing) and --check (never let
|
|
449
|
+
# a growth check pass over nothing to check).
|
|
450
|
+
print(
|
|
451
|
+
f"::error::error-branch coverage: {coverage_note} — the merged "
|
|
452
|
+
"coverage report describes 0 files. Refusing to treat that as "
|
|
453
|
+
"clean: a report with nothing in it is not evidence the tree is "
|
|
454
|
+
"covered, it is evidence nothing was measured (a "
|
|
455
|
+
"[tool.coverage.run] source pointed at the wrong tree, a "
|
|
456
|
+
"truncated or hand-staged artefact, a coverage run against an "
|
|
457
|
+
"empty package). Fix the coverage run and try again.",
|
|
458
|
+
file=sys.stderr,
|
|
459
|
+
)
|
|
460
|
+
return 2
|
|
461
|
+
|
|
431
462
|
found = unexecuted(coverage, source_root)
|
|
432
463
|
keys = sorted({b.key() for b in found})
|
|
433
464
|
|
|
@@ -436,7 +467,7 @@ def main() -> int:
|
|
|
436
467
|
baseline_path.write_text(
|
|
437
468
|
json.dumps({"total": len(keys), "branches": keys}, indent=2) + "\n",
|
|
438
469
|
)
|
|
439
|
-
print(f"baseline written: {len(keys)} unexecuted error branches")
|
|
470
|
+
print(f"baseline written: {len(keys)} unexecuted error branches ({coverage_note})")
|
|
440
471
|
return 0
|
|
441
472
|
|
|
442
473
|
baseline = load_baseline(baseline_path)
|
|
@@ -418,16 +418,47 @@ def main() -> int:
|
|
|
418
418
|
file=sys.stderr,
|
|
419
419
|
)
|
|
420
420
|
|
|
421
|
+
reports = [json.loads(p.read_text()) for p in present]
|
|
422
|
+
coverage = merge_coverage(reports)
|
|
423
|
+
file_count = len(coverage.get("files", {}))
|
|
424
|
+
|
|
421
425
|
# Restated in every summary line below (not just the warning above) so the
|
|
422
426
|
# denominator survives even if the warning scrolls past unread: a reader
|
|
423
427
|
# who sees only the final line still learns how much of the requested
|
|
424
|
-
# picture this result is actually over.
|
|
425
|
-
|
|
428
|
+
# picture this result is actually over. The file count is part of that
|
|
429
|
+
# denominator, not just the artefact count — see the fail-closed check
|
|
430
|
+
# immediately below for why (#1657).
|
|
431
|
+
coverage_note = (
|
|
432
|
+
f"{len(present)}/{len(coverage_paths)} coverage artefact(s), {file_count} file(s)"
|
|
433
|
+
)
|
|
426
434
|
if absent:
|
|
427
435
|
coverage_note += f" — MISSING: {', '.join(str(p) for p in absent)}"
|
|
428
436
|
|
|
429
|
-
|
|
430
|
-
|
|
437
|
+
if file_count == 0:
|
|
438
|
+
# #1657: an artefact can be present and valid JSON while still
|
|
439
|
+
# describing NOTHING — `{"files": {}}`, or `files` absent entirely.
|
|
440
|
+
# That reads identically to "everything is covered" to the logic
|
|
441
|
+
# below (found == [], keys == [], nothing NEW), so --check would
|
|
442
|
+
# exit 0 over a report that examined zero files: a fail-open in the
|
|
443
|
+
# gate that exists specifically to catch fail-opens. Every other
|
|
444
|
+
# soft-open path here (no baseline, a missing --coverage file)
|
|
445
|
+
# announces itself loudly and still proceeds; this one cannot
|
|
446
|
+
# proceed at all, because there is no denominator left to reason
|
|
447
|
+
# over. Fail closed rather than fail open: refuse both --write
|
|
448
|
+
# (never baseline a measurement of nothing) and --check (never let
|
|
449
|
+
# a growth check pass over nothing to check).
|
|
450
|
+
print(
|
|
451
|
+
f"::error::error-branch coverage: {coverage_note} — the merged "
|
|
452
|
+
"coverage report describes 0 files. Refusing to treat that as "
|
|
453
|
+
"clean: a report with nothing in it is not evidence the tree is "
|
|
454
|
+
"covered, it is evidence nothing was measured (a "
|
|
455
|
+
"[tool.coverage.run] source pointed at the wrong tree, a "
|
|
456
|
+
"truncated or hand-staged artefact, a coverage run against an "
|
|
457
|
+
"empty package). Fix the coverage run and try again.",
|
|
458
|
+
file=sys.stderr,
|
|
459
|
+
)
|
|
460
|
+
return 2
|
|
461
|
+
|
|
431
462
|
found = unexecuted(coverage, source_root)
|
|
432
463
|
keys = sorted({b.key() for b in found})
|
|
433
464
|
|
|
@@ -436,7 +467,7 @@ def main() -> int:
|
|
|
436
467
|
baseline_path.write_text(
|
|
437
468
|
json.dumps({"total": len(keys), "branches": keys}, indent=2) + "\n",
|
|
438
469
|
)
|
|
439
|
-
print(f"baseline written: {len(keys)} unexecuted error branches")
|
|
470
|
+
print(f"baseline written: {len(keys)} unexecuted error branches ({coverage_note})")
|
|
440
471
|
return 0
|
|
441
472
|
|
|
442
473
|
baseline = load_baseline(baseline_path)
|
|
@@ -574,11 +574,11 @@ wheels = [
|
|
|
574
574
|
|
|
575
575
|
[[package]]
|
|
576
576
|
name = "pip"
|
|
577
|
-
version = "26.1
|
|
577
|
+
version = "26.2.1"
|
|
578
578
|
source = { registry = "https://pypi.org/simple" }
|
|
579
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
579
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ae/15/4500e320e6b101ec3b719ae85b697d9940b6cda672bc555bd6016fc60c6f/pip-26.2.1.tar.gz", hash = "sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f", size = 1848877, upload-time = "2026-08-04T22:51:14.148Z" }
|
|
580
580
|
wheels = [
|
|
581
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
581
|
+
{ url = "https://files.pythonhosted.org/packages/f3/6e/1736e5b4ae2b778ef2f81c47d797de9f891d4d8acb047a24ca37a60294dd/pip-26.2.1-py3-none-any.whl", hash = "sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e", size = 1816632, upload-time = "2026-08-04T22:51:12.472Z" },
|
|
582
582
|
]
|
|
583
583
|
|
|
584
584
|
[[package]]
|