Skip to content

Commit ea52d47

Browse files
authored
[codex] Add focused example artifact checks (#63)
1 parent 3b1edb8 commit ea52d47

4 files changed

Lines changed: 117 additions & 8 deletions

File tree

tools/sbom-diff-and-risk/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ JSON, Markdown, summary, policy sidecar, and SARIF examples with:
326326
python scripts/regenerate-example-artifacts.py
327327
python scripts/regenerate-example-artifacts.py --check
328328
```
329+
330+
Use `python scripts/regenerate-example-artifacts.py --list` and `--only SLUG`
331+
for focused checks such as `--only requirements`.
329332

330333
## Enforcement Mode
331334

tools/sbom-diff-and-risk/docs/example-artifact-regeneration.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ python scripts/regenerate-example-artifacts.py --check
4646
The test suite runs this check mode so stale local JSON, Markdown, summary, or
4747
policy-sidecar examples fail predictably.
4848

49+
Use `--list` to see the available artifact set slugs:
50+
51+
```powershell
52+
python scripts/regenerate-example-artifacts.py --list
53+
```
54+
55+
Use `--only SLUG` to regenerate or check a focused subset:
56+
57+
```powershell
58+
python scripts/regenerate-example-artifacts.py --check --only requirements
59+
```
60+
61+
`--only` can be repeated when a change affects more than one artifact set.
62+
4963
## Boundaries
5064

5165
The regeneration script covers no-network JSON, Markdown, summary, policy

tools/sbom-diff-and-risk/scripts/regenerate-example-artifacts.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
@dataclass(frozen=True)
1515
class ExampleArtifactSet:
16+
slug: str
1617
name: str
1718
base_args: tuple[str, ...]
1819
outputs: tuple[tuple[str, str], ...]
@@ -22,6 +23,7 @@ class ExampleArtifactSet:
2223

2324
ARTIFACT_SETS: tuple[ExampleArtifactSet, ...] = (
2425
ExampleArtifactSet(
26+
slug="cyclonedx",
2527
name="cyclonedx report, summary, and markdown",
2628
base_args=(
2729
"--before",
@@ -38,6 +40,7 @@ class ExampleArtifactSet:
3840
),
3941
),
4042
ExampleArtifactSet(
43+
slug="policy-warn",
4144
name="warn-only policy report",
4245
base_args=(
4346
"--before",
@@ -53,6 +56,7 @@ class ExampleArtifactSet:
5356
),
5457
),
5558
ExampleArtifactSet(
59+
slug="policy-fail",
5660
name="blocking policy report and sidecar",
5761
base_args=(
5862
"--before",
@@ -70,6 +74,7 @@ class ExampleArtifactSet:
7074
expected_exit_codes=(1,),
7175
),
7276
ExampleArtifactSet(
77+
slug="requirements",
7378
name="requirements report",
7479
base_args=(
7580
"--before",
@@ -85,6 +90,7 @@ class ExampleArtifactSet:
8590
),
8691
),
8792
ExampleArtifactSet(
93+
slug="sarif",
8894
name="strict-policy SARIF report",
8995
base_args=(
9096
"--before",
@@ -112,28 +118,73 @@ def main(argv: Sequence[str] | None = None) -> int:
112118
action="store_true",
113119
help="Generate artifacts into a temporary directory and fail if checked-in examples are stale.",
114120
)
121+
parser.add_argument(
122+
"--list",
123+
action="store_true",
124+
help="List available artifact set slugs and exit.",
125+
)
126+
parser.add_argument(
127+
"--only",
128+
action="append",
129+
default=[],
130+
metavar="SLUG",
131+
help="Regenerate or check only one artifact set slug. Repeat to select multiple sets.",
132+
)
115133
args = parser.parse_args(argv)
116134

117135
project_root = Path(__file__).resolve().parents[1]
136+
artifact_sets = _select_artifact_sets(args.only, parser)
137+
if args.list:
138+
_print_artifact_sets(artifact_sets)
139+
return 0
118140
if args.check:
119141
with tempfile.TemporaryDirectory(prefix="sbom-diff-risk-examples-") as temp_dir:
120-
return _check_artifacts(project_root, Path(temp_dir))
121-
return _write_artifacts(project_root, project_root / "examples")
142+
return _check_artifacts(project_root, Path(temp_dir), artifact_sets)
143+
return _write_artifacts(project_root, project_root / "examples", artifact_sets)
144+
145+
146+
def _select_artifact_sets(
147+
selected_slugs: Sequence[str],
148+
parser: argparse.ArgumentParser,
149+
) -> tuple[ExampleArtifactSet, ...]:
150+
if not selected_slugs:
151+
return ARTIFACT_SETS
152+
153+
by_slug = {artifact_set.slug: artifact_set for artifact_set in ARTIFACT_SETS}
154+
unknown = [slug for slug in selected_slugs if slug not in by_slug]
155+
if unknown:
156+
parser.error(f"unknown artifact set slug: {unknown[0]}")
157+
158+
return tuple(by_slug[slug] for slug in selected_slugs)
159+
160+
161+
def _print_artifact_sets(artifact_sets: Sequence[ExampleArtifactSet]) -> None:
162+
for artifact_set in artifact_sets:
163+
outputs = ", ".join(output_name for _, output_name in artifact_set.outputs)
164+
print(f"{artifact_set.slug}: {artifact_set.name} ({outputs})")
122165

123166

124-
def _write_artifacts(project_root: Path, output_root: Path) -> int:
125-
for artifact_set in ARTIFACT_SETS:
167+
def _write_artifacts(
168+
project_root: Path,
169+
output_root: Path,
170+
artifact_sets: Sequence[ExampleArtifactSet],
171+
) -> int:
172+
for artifact_set in artifact_sets:
126173
_run_artifact_set(project_root, output_root, artifact_set)
127-
print(f"generated: {artifact_set.name}")
174+
print(f"generated: {artifact_set.slug}")
128175
return 0
129176

130177

131-
def _check_artifacts(project_root: Path, output_root: Path) -> int:
132-
_write_artifacts(project_root, output_root)
178+
def _check_artifacts(
179+
project_root: Path,
180+
output_root: Path,
181+
artifact_sets: Sequence[ExampleArtifactSet],
182+
) -> int:
183+
_write_artifacts(project_root, output_root, artifact_sets)
133184

134185
examples_dir = project_root / "examples"
135186
stale_files: list[str] = []
136-
for artifact_set in ARTIFACT_SETS:
187+
for artifact_set in artifact_sets:
137188
for _, output_name in artifact_set.outputs:
138189
expected = (examples_dir / output_name).read_text(encoding="utf-8")
139190
generated = (output_root / output_name).read_text(encoding="utf-8")

tools/sbom-diff-and-risk/tests/test_example_artifacts.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,44 @@ def test_regenerate_example_artifacts_check_mode_passes() -> None:
2121

2222
assert result.returncode == 0, result.stdout + result.stderr
2323
assert "all checked example artifacts are up to date" in result.stdout
24+
25+
26+
def test_regenerate_example_artifacts_can_list_artifact_sets() -> None:
27+
project_root = Path(__file__).resolve().parents[1]
28+
29+
result = subprocess.run(
30+
[
31+
sys.executable,
32+
str(project_root / "scripts" / "regenerate-example-artifacts.py"),
33+
"--list",
34+
],
35+
cwd=project_root,
36+
text=True,
37+
capture_output=True,
38+
)
39+
40+
assert result.returncode == 0, result.stdout + result.stderr
41+
assert "cyclonedx:" in result.stdout
42+
assert "requirements:" in result.stdout
43+
assert "sarif:" in result.stdout
44+
45+
46+
def test_regenerate_example_artifacts_can_check_one_artifact_set() -> None:
47+
project_root = Path(__file__).resolve().parents[1]
48+
49+
result = subprocess.run(
50+
[
51+
sys.executable,
52+
str(project_root / "scripts" / "regenerate-example-artifacts.py"),
53+
"--check",
54+
"--only",
55+
"requirements",
56+
],
57+
cwd=project_root,
58+
text=True,
59+
capture_output=True,
60+
)
61+
62+
assert result.returncode == 0, result.stdout + result.stderr
63+
assert "generated: requirements" in result.stdout
64+
assert "generated: cyclonedx" not in result.stdout

0 commit comments

Comments
 (0)