lib: oeqa: spdx: Add tests for extra options

Adds a test for several of the extra options provided by the SPDX
classes. In particular, these are the options that can produce
non-reproducible results, so are not enabled by default in OE core. This
test takes care to configure the build so that the tests do run in a
reproducible manner so that pre-built test objects can be pulled from
sstate

(From OE-Core rev: 72ee311d4f74499674a29223fb02d4e774097a54)

Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
(cherry picked from commit 14f534f15f7fe6362723d7f064d39783c5bd758f)
Signed-off-by: Kamel Bouhara (Schneider Electric) <kamel.bouhara@bootlin.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
This commit is contained in:
Joshua Watt
2025-11-07 14:14:47 +01:00
committed by Steve Sakoman
parent 568c1afab4
commit 1919d76f68

View File

@@ -7,9 +7,11 @@
import json import json
import os import os
import textwrap import textwrap
import hashlib
from pathlib import Path from pathlib import Path
from oeqa.selftest.case import OESelftestTestCase from oeqa.selftest.case import OESelftestTestCase
from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars, runCmd from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars, runCmd
import oe.spdx30
class SPDX22Check(OESelftestTestCase): class SPDX22Check(OESelftestTestCase):
@@ -73,8 +75,6 @@ class SPDX3CheckBase(object):
""" """
def check_spdx_file(self, filename): def check_spdx_file(self, filename):
import oe.spdx30
self.assertExists(filename) self.assertExists(filename)
# Read the file # Read the file
@@ -86,13 +86,16 @@ class SPDX3CheckBase(object):
return objset return objset
def check_recipe_spdx(self, target_name, spdx_path, *, task=None, extraconf=""): def check_recipe_spdx(self, target_name, spdx_path, *, task=None, extraconf=""):
config = textwrap.dedent( config = (
f"""\ textwrap.dedent(
INHERIT:remove = "create-spdx" f"""\
INHERIT += "{self.SPDX_CLASS}" INHERIT:remove = "create-spdx"
{extraconf} INHERIT += "{self.SPDX_CLASS}"
""" """
)
+ textwrap.dedent(extraconf)
) )
self.write_config(config) self.write_config(config)
if task: if task:
@@ -120,11 +123,17 @@ class SPDX3CheckBase(object):
return self.check_spdx_file(filename) return self.check_spdx_file(filename)
def check_objset_missing_ids(self, objset): def check_objset_missing_ids(self, objset):
if objset.missing_ids: for o in objset.foreach_type(oe.spdx30.SpdxDocument):
doc = o
break
else:
self.assertTrue(False, "Unable to find SpdxDocument")
missing_ids = objset.missing_ids - set(i.externalSpdxId for i in doc.import_)
if missing_ids:
self.assertTrue( self.assertTrue(
False, False,
"The following SPDXIDs are unresolved:\n " "The following SPDXIDs are unresolved:\n " + "\n ".join(missing_ids),
+ "\n ".join(objset.missing_ids),
) )
@@ -188,12 +197,93 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase):
objset = self.check_recipe_spdx( objset = self.check_recipe_spdx(
"baremetal-helloworld", "baremetal-helloworld",
"{DEPLOY_DIR_IMAGE}/baremetal-helloworld-image-{MACHINE}.spdx.json", "{DEPLOY_DIR_IMAGE}/baremetal-helloworld-image-{MACHINE}.spdx.json",
extraconf=textwrap.dedent( extraconf="""\
"""\
TCLIBC = "baremetal" TCLIBC = "baremetal"
""" """,
),
) )
# Document should be fully linked # Document should be fully linked
self.check_objset_missing_ids(objset) self.check_objset_missing_ids(objset)
def test_extra_opts(self):
HOST_SPDXID = "http://foo.bar/spdx/bar2"
EXTRACONF = textwrap.dedent(
f"""\
SPDX_INVOKED_BY_name = "CI Tool"
SPDX_INVOKED_BY_type = "software"
SPDX_ON_BEHALF_OF_name = "John Doe"
SPDX_ON_BEHALF_OF_type = "person"
SPDX_ON_BEHALF_OF_id_email = "John.Doe@noreply.com"
SPDX_PACKAGE_SUPPLIER_name = "ACME Embedded Widgets"
SPDX_PACKAGE_SUPPLIER_type = "organization"
SPDX_AUTHORS += "authorA"
SPDX_AUTHORS_authorA_ref = "SPDX_ON_BEHALF_OF"
SPDX_BUILD_HOST = "host"
SPDX_IMPORTS += "host"
SPDX_IMPORTS_host_spdxid = "{HOST_SPDXID}"
SPDX_INCLUDE_BUILD_VARIABLES = "1"
SPDX_INCLUDE_BITBAKE_PARENT_BUILD = "1"
SPDX_INCLUDE_TIMESTAMPS = "1"
SPDX_PRETTY = "1"
"""
)
extraconf_hash = hashlib.sha1(EXTRACONF.encode("utf-8")).hexdigest()
objset = self.check_recipe_spdx(
"core-image-minimal",
"{DEPLOY_DIR_IMAGE}/core-image-minimal-{MACHINE}.rootfs.spdx.json",
# Many SPDX variables do not trigger a rebuild, since they are
# intended to record information at the time of the build. As such,
# the extra configuration alone may not trigger a rebuild, and even
# if it does, the task hash won't necessarily be unique. In order
# to make sure rebuilds happen, but still allow these test objects
# to be pulled from sstate (e.g. remain reproducible), change the
# namespace prefix to include the hash of the extra configuration
extraconf=textwrap.dedent(
f"""\
SPDX_NAMESPACE_PREFIX = "http://spdx.org/spdxdocs/{extraconf_hash}"
"""
)
+ EXTRACONF,
)
# Document should be fully linked
self.check_objset_missing_ids(objset)
for o in objset.foreach_type(oe.spdx30.SoftwareAgent):
if o.name == "CI Tool":
break
else:
self.assertTrue(False, "Unable to find software tool")
for o in objset.foreach_type(oe.spdx30.Person):
if o.name == "John Doe":
break
else:
self.assertTrue(False, "Unable to find person")
for o in objset.foreach_type(oe.spdx30.Organization):
if o.name == "ACME Embedded Widgets":
break
else:
self.assertTrue(False, "Unable to find organization")
for o in objset.foreach_type(oe.spdx30.SpdxDocument):
doc = o
break
else:
self.assertTrue(False, "Unable to find SpdxDocument")
for i in doc.import_:
if i.externalSpdxId == HOST_SPDXID:
break
else:
self.assertTrue(False, "Unable to find imported Host SpdxID")