Commit 4adb893704

4adb893704fe2f443ff48d6983da69e4295eaace

parent: 3b24b70d69

Unsigned

cmc <hello@cleberg.net> · 2026-08-09 01:32 UTC

Pin CI actions to SHA, simplify main(), tidy exceptions and tests

Layout: unified · split

.github/workflows/release.yml +2 −2
@@ -11,7 +11,7 @@ jobs:
1111 steps:
1212 - uses: actions/checkout@v5
1313 - name: Install uv
14 uses: astral-sh/setup-uv@v6
14 uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
1515 - name: Build
1616 run: uv build
1717 - name: Check
@@ -33,4 +33,4 @@ jobs:
3333 name: dist
3434 path: dist/
3535 - name: Publish to PyPI
36 uses: pypa/gh-action-pypi-publish@release/v1
36 uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
control_coverage/cli.py +8 −4
@@ -117,11 +117,11 @@ def _now() -> str:
117117 return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
118118
119119
120def _build_report(paths, args, scp, names, subject):
120def _build_report(paths, scp, names, subject):
121121 """Load a corpus from *paths* and evaluate it into a CoverageReport."""
122122 try:
123123 observations = corpus.load_corpus(paths)
124 except (ValueError, FileNotFoundError, OSError) as exc:
124 except (ValueError, OSError) as exc:
125125 raise SystemExit(f"error: {exc}") from None
126126 catalogs = catalog.load_frameworks(names)
127127 return evaluate(catalogs, observations, scope=scp, subject=subject, generated_at=_now())
@@ -135,7 +135,7 @@ def main(argv: list[str] | None = None) -> int:
135135
136136 try:
137137 observations = corpus.load_corpus(args.reports)
138 except (ValueError, FileNotFoundError, OSError) as exc:
138 except (ValueError, OSError) as exc:
139139 raise SystemExit(f"error: {exc}") from None
140140
141141 scp = scope.load(args.scope) if args.scope else scope.empty()
@@ -159,6 +159,10 @@ def main(argv: list[str] | None = None) -> int:
159159 _print_blind_spots(report)
160160 return _exit_code(report, args.fail_under)
161161
162 return _default_mode(args, report, subject)
163
164
165def _default_mode(args, report, subject) -> int:
162166 formats = [f.strip() for f in args.format.split(",") if f.strip()]
163167 if args.out:
164168 out_dir = Path(args.out)
@@ -180,7 +184,7 @@ def main(argv: list[str] | None = None) -> int:
180184def _trend_mode(args, scp, names, subject, current) -> int:
181185 from . import trend
182186
183 baseline = _build_report([args.baseline], args, scp, names, subject)
187 baseline = _build_report([args.baseline], scp, names, subject)
184188 tr = trend.compare(baseline, current)
185189
186190 formats = [f.strip() for f in args.format.split(",") if f.strip()]
control_coverage/coverage.py +1 −1
@@ -74,7 +74,7 @@ class FrameworkCoverage:
7474
7575 @property
7676 def counts(self) -> dict[str, int]:
77 counts = {s: 0 for s in STATE_ORDER}
77 counts = dict.fromkeys(STATE_ORDER, 0)
7878 for r in self.results:
7979 counts[r.state] += 1
8080 return counts
control_coverage/trend.py +1 −1
@@ -81,7 +81,7 @@ class FrameworkTrend:
8181
8282 @property
8383 def counts(self) -> dict[str, int]:
84 counts = {c: 0 for c in CATEGORY_ORDER}
84 counts = dict.fromkeys(CATEGORY_ORDER, 0)
8585 for d in self.deltas:
8686 counts[d.category] += 1
8787 return counts
tests/test_cli.py +2 −1
@@ -85,7 +85,8 @@ def test_trend_html_output(tmp_path):
8585 cli.main([GITHUB, AWS, "--framework", "SOC2", "--baseline", BASELINE,
8686 "--format", "html,json", "--out", str(tmp_path)])
8787 names = {p.name for p in tmp_path.iterdir()}
88 assert "trend.html" in names and "trend.json" in names
88 assert "trend.html" in names
89 assert "trend.json" in names
8990
9091
9192def test_crosswalk_mode(capsys):
tests/test_crosswalk.py +2 −1
@@ -63,5 +63,6 @@ def test_html_is_self_contained():
6363 html = crosswalk.render_html(_crosswalk(["SOC2", "ISO", "NIST"]))
6464 assert html.startswith("<!doctype html>")
6565 assert "<style>" in html
66 assert "http://" not in html and "https://" not in html
66 assert "http://" not in html
67 assert "https://" not in html
6768 assert "github.org.require-2fa" in html
tests/test_reporters.py +4 −2
@@ -47,7 +47,8 @@ def test_html_is_self_contained():
4747 html = reporters.render(_report(), "html")
4848 assert html.startswith("<!doctype html>")
4949 assert "<style>" in html
50 assert "http://" not in html and "https://" not in html # no external assets
50 assert "http://" not in html # no external assets
51 assert "https://" not in html
5152
5253
5354def test_soa_lists_applicability_and_status():
@@ -60,5 +61,6 @@ def test_soa_lists_applicability_and_status():
6061def test_unknown_format_raises():
6162 import pytest
6263
64 report = _report()
6365 with pytest.raises(ValueError, match="unknown format"):
64 reporters.render(_report(), "pdf")
66 reporters.render(report, "pdf")
tests/test_trend.py +2 −1
@@ -71,5 +71,6 @@ def test_html_is_self_contained():
7171 html = trend.render_html(_compare())
7272 assert html.startswith("<!doctype html>")
7373 assert "<style>" in html
74 assert "http://" not in html and "https://" not in html
74 assert "http://" not in html
75 assert "https://" not in html
7576 assert "CC9.2" in html # a changed control shows up