Files
buildroot/support/testing/tests/utils/test_generate_cyclonedx.py
Martin Willi cc41cc3fcd utils/generate-cyclonedx: remove indirect dependencies from root component
Commit dc4af8bfa9 ("utils/generate-cyclonedx: use direct dependencies")
removes indirect dependencies from any listed component, as required by
CycloneDX. The root component, however, still includes indirect dependencies,
as it just takes the components from the show-info output.

Fix this by collecting all component dependencies, and then filter the root
component dependencies to include direct dependencies only.

Signed-off-by: Martin Willi <martin@strongswan.org>
Acked-By: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Arnout Vandecappelle <arnout@rnout.be>
2026-05-30 15:54:05 +02:00

143 lines
5.3 KiB
Python

"""Unit tests for utils/generate-cyclonedx."""
import json
import os
import subprocess
import tempfile
import unittest
from pathlib import Path
import infra
PATCH = "support/testing/tests/utils/test_generate_cyclonedx/0001-cve_upstream.patch"
SCHEMA_LICENSES = ["MIT", "Apache-2.0", "GPL-3.0-only"]
class TestGenerateCycloneDX(unittest.TestCase):
def setUp(self):
# Provide a fake SPDX schema so the script never hits the network.
self.schema_dir = tempfile.TemporaryDirectory()
self.addCleanup(self.schema_dir.cleanup)
cyclonedx_dir = Path(self.schema_dir.name) / "cyclonedx"
cyclonedx_dir.mkdir(parents=True)
schema_path = cyclonedx_dir / "spdx-1.6.schema.json"
schema_path.write_text(json.dumps({"enum": SCHEMA_LICENSES}))
self.env = os.environ.copy()
self.env["BR2_DL_DIR"] = self.schema_dir.name
self.script = infra.basepath("utils/generate-cyclonedx")
self.cwd = infra.basepath()
def _make_show_info(self) -> dict:
return {
"package-foo": {
"name": "foo",
"version": "1.2",
"type": "target",
"virtual": False,
"licenses": "MIT",
"cpe-id": "cpe:2.3:a:example:foo:1.2:*:*:*:*:*:*:*",
"patches": [PATCH],
"provides": ["package-virtual"],
"dependencies": ["skeleton-baz", "package-bar"],
"ignore_cves": ["CVE-2025-0001"],
"package_dir": "package/package-foo",
},
"skeleton-baz": {
"name": "skeleton-baz",
"version": "0.1",
"type": "target",
"virtual": False,
"licenses": "Apache-2.0",
"dependencies": [],
"package_dir": "package/skeleton-baz",
},
"package-bar": {
"name": "bar",
"version": "0.2",
"type": "target",
"virtual": False,
"licenses": "MIT",
"ignore_cves": ["CVE-2025-0002"],
"dependencies": ["package-virtual"],
"package_dir": "package/package-bar",
},
"host-tool": {
"name": "host-tool",
"version": "0.3",
"type": "host",
"virtual": False,
"licenses": "GPL-3.0-only",
"dependencies": [],
"package_dir": "package/host-tool",
},
"package-virtual": {
"name": "virtual-provider",
"virtual": True,
"type": "target",
"dependencies": ["package-foo"],
"package_dir": "package/package-virtual",
},
}
def _run_script(self, extra_args=(), show_info=None):
data = show_info if show_info is not None else self._make_show_info()
completed = subprocess.run(
[self.script, *extra_args],
cwd=self.cwd,
env=self.env,
input=json.dumps(data),
text=True,
capture_output=True,
check=True,
)
return json.loads(completed.stdout)
def _find_component(self, result: dict, name: str) -> dict:
for component in result["components"]:
if component["bom-ref"] == name:
return component
self.fail(f"component {name} missing")
def test_default(self):
result = self._run_script()
self.assertEqual(len(result["components"]), 4)
self.assertIn("vulnerabilities", result)
vulnerabilities = {v["id"]: v for v in result["vulnerabilities"]}
self.assertEqual(len(vulnerabilities), 2)
self.assertEqual(vulnerabilities["CVE-2025-0001"]["analysis"]["state"], "resolved_with_pedigree")
self.assertEqual(vulnerabilities["CVE-2025-0002"]["analysis"]["state"], "in_triage")
foo = self._find_component(result, "package-foo")
patch = foo["pedigree"]["patches"][0]
self.assertIn("text", patch["diff"])
self.assertIn("content", patch["diff"]["text"])
host = self._find_component(result, "host-tool")
self.assertEqual(host["properties"][0]["value"], "host")
names = {c["bom-ref"] for c in result["components"]}
self.assertIn("skeleton-baz", names)
self.assertIn("package-bar", names)
self.assertNotIn("package-virtual", names)
foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])
bar_deps = next(d for d in result["dependencies"] if d["ref"] == "package-bar")
self.assertEqual(bar_deps["dependsOn"], ["package-foo"])
project_deps = next(d for d in result["dependencies"] if d["ref"] == "buildroot")
self.assertEqual(project_deps["dependsOn"], ["host-tool", "package-foo"])
def test_virtual(self):
result = self._run_script(["--virtual"])
names = {c["bom-ref"] for c in result["components"]}
self.assertEqual(names, {"package-foo", "skeleton-baz", "host-tool", "package-virtual", "package-bar"})
foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])