summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa/selftest/cases/spdx.py
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oeqa/selftest/cases/spdx.py')
-rw-r--r--meta/lib/oeqa/selftest/cases/spdx.py288
1 files changed, 288 insertions, 0 deletions
diff --git a/meta/lib/oeqa/selftest/cases/spdx.py b/meta/lib/oeqa/selftest/cases/spdx.py
new file mode 100644
index 0000000000..8cd4e83ca2
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/spdx.py
@@ -0,0 +1,288 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import json
8import os
9import textwrap
10import hashlib
11from pathlib import Path
12from oeqa.selftest.case import OESelftestTestCase
13from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars, runCmd
14import oe.spdx30
15
16
17class SPDX22Check(OESelftestTestCase):
18 @classmethod
19 def setUpClass(cls):
20 super().setUpClass()
21 bitbake("python3-spdx-tools-native")
22 bitbake("-c addto_recipe_sysroot python3-spdx-tools-native")
23
24 def check_recipe_spdx(self, high_level_dir, spdx_file, target_name):
25 config = textwrap.dedent(
26 """\
27 INHERIT:remove = "create-spdx"
28 INHERIT += "create-spdx-2.2"
29 """
30 )
31 self.write_config(config)
32
33 deploy_dir = get_bb_var("DEPLOY_DIR")
34 arch_dir = get_bb_var("PACKAGE_ARCH", target_name)
35 spdx_version = get_bb_var("SPDX_VERSION")
36 # qemux86-64 creates the directory qemux86_64
37 #arch_dir = arch_var.replace("-", "_")
38
39 full_file_path = os.path.join(
40 deploy_dir, "spdx", spdx_version, arch_dir, high_level_dir, spdx_file
41 )
42
43 try:
44 os.remove(full_file_path)
45 except FileNotFoundError:
46 pass
47
48 bitbake("%s -c create_spdx" % target_name)
49
50 def check_spdx_json(filename):
51 with open(filename) as f:
52 report = json.load(f)
53 self.assertNotEqual(report, None)
54 self.assertNotEqual(report["SPDXID"], None)
55
56 python = os.path.join(
57 get_bb_var("STAGING_BINDIR", "python3-spdx-tools-native"),
58 "nativepython3",
59 )
60 validator = os.path.join(
61 get_bb_var("STAGING_BINDIR", "python3-spdx-tools-native"), "pyspdxtools"
62 )
63 result = runCmd("{} {} -i {}".format(python, validator, filename))
64
65 self.assertExists(full_file_path)
66 result = check_spdx_json(full_file_path)
67
68 def test_spdx_base_files(self):
69 self.check_recipe_spdx("packages", "base-files.spdx.json", "base-files")
70
71 def test_spdx_tar(self):
72 self.check_recipe_spdx("packages", "tar.spdx.json", "tar")
73
74
75class SPDX3CheckBase(object):
76 """
77 Base class for checking SPDX 3 based tests
78 """
79
80 def check_spdx_file(self, filename):
81 self.assertExists(filename)
82
83 # Read the file
84 objset = oe.spdx30.SHACLObjectSet()
85 with open(filename, "r") as f:
86 d = oe.spdx30.JSONLDDeserializer()
87 d.read(f, objset)
88
89 return objset
90
91 def check_recipe_spdx(self, target_name, spdx_path, *, task=None, extraconf=""):
92 config = (
93 textwrap.dedent(
94 f"""\
95 INHERIT:remove = "create-spdx"
96 INHERIT += "{self.SPDX_CLASS}"
97 """
98 )
99 + textwrap.dedent(extraconf)
100 )
101
102 self.write_config(config)
103
104 if task:
105 bitbake(f"-c {task} {target_name}")
106 else:
107 bitbake(target_name)
108
109 filename = spdx_path.format(
110 **get_bb_vars(
111 [
112 "DEPLOY_DIR_IMAGE",
113 "DEPLOY_DIR_SPDX",
114 "MACHINE",
115 "MACHINE_ARCH",
116 "SDKMACHINE",
117 "SDK_DEPLOY",
118 "SPDX_VERSION",
119 "SSTATE_PKGARCH",
120 "TOOLCHAIN_OUTPUTNAME",
121 ],
122 target_name,
123 )
124 )
125
126 return self.check_spdx_file(filename)
127
128 def check_objset_missing_ids(self, objset):
129 for o in objset.foreach_type(oe.spdx30.SpdxDocument):
130 doc = o
131 break
132 else:
133 self.assertTrue(False, "Unable to find SpdxDocument")
134
135 missing_ids = objset.missing_ids - set(i.externalSpdxId for i in doc.import_)
136 if missing_ids:
137 self.assertTrue(
138 False,
139 "The following SPDXIDs are unresolved:\n " + "\n ".join(missing_ids),
140 )
141
142
143class SPDX30Check(SPDX3CheckBase, OESelftestTestCase):
144 SPDX_CLASS = "create-spdx-3.0"
145
146 def test_base_files(self):
147 self.check_recipe_spdx(
148 "base-files",
149 "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/package-base-files.spdx.json",
150 )
151
152 def test_gcc_include_source(self):
153 objset = self.check_recipe_spdx(
154 "gcc",
155 "{DEPLOY_DIR_SPDX}/{SSTATE_PKGARCH}/recipes/recipe-gcc.spdx.json",
156 extraconf="""\
157 SPDX_INCLUDE_SOURCES = "1"
158 """,
159 )
160
161 gcc_pv = get_bb_var("PV", "gcc")
162 filename = f"gcc-{gcc_pv}/README"
163 found = False
164 for software_file in objset.foreach_type(oe.spdx30.software_File):
165 if software_file.name == filename:
166 found = True
167 self.logger.info(
168 f"The spdxId of {filename} in recipe-gcc.spdx.json is {software_file.spdxId}"
169 )
170 break
171
172 self.assertTrue(
173 found, f"Not found source file {filename} in recipe-gcc.spdx.json\n"
174 )
175
176 def test_core_image_minimal(self):
177 objset = self.check_recipe_spdx(
178 "core-image-minimal",
179 "{DEPLOY_DIR_IMAGE}/core-image-minimal-{MACHINE}.rootfs.spdx.json",
180 )
181
182 # Document should be fully linked
183 self.check_objset_missing_ids(objset)
184
185 def test_core_image_minimal_sdk(self):
186 objset = self.check_recipe_spdx(
187 "core-image-minimal",
188 "{SDK_DEPLOY}/{TOOLCHAIN_OUTPUTNAME}.spdx.json",
189 task="populate_sdk",
190 )
191
192 # Document should be fully linked
193 self.check_objset_missing_ids(objset)
194
195 def test_baremetal_helloworld(self):
196 objset = self.check_recipe_spdx(
197 "baremetal-helloworld",
198 "{DEPLOY_DIR_IMAGE}/baremetal-helloworld-image-{MACHINE}.spdx.json",
199 extraconf="""\
200 TCLIBC = "baremetal"
201 """,
202 )
203
204 # Document should be fully linked
205 self.check_objset_missing_ids(objset)
206
207 def test_extra_opts(self):
208 HOST_SPDXID = "http://foo.bar/spdx/bar2"
209
210 EXTRACONF = textwrap.dedent(
211 f"""\
212 SPDX_INVOKED_BY_name = "CI Tool"
213 SPDX_INVOKED_BY_type = "software"
214
215 SPDX_ON_BEHALF_OF_name = "John Doe"
216 SPDX_ON_BEHALF_OF_type = "person"
217 SPDX_ON_BEHALF_OF_id_email = "John.Doe@noreply.com"
218
219 SPDX_PACKAGE_SUPPLIER_name = "ACME Embedded Widgets"
220 SPDX_PACKAGE_SUPPLIER_type = "organization"
221
222 SPDX_AUTHORS += "authorA"
223 SPDX_AUTHORS_authorA_ref = "SPDX_ON_BEHALF_OF"
224
225 SPDX_BUILD_HOST = "host"
226
227 SPDX_IMPORTS += "host"
228 SPDX_IMPORTS_host_spdxid = "{HOST_SPDXID}"
229
230 SPDX_INCLUDE_BUILD_VARIABLES = "1"
231 SPDX_INCLUDE_BITBAKE_PARENT_BUILD = "1"
232 SPDX_INCLUDE_TIMESTAMPS = "1"
233
234 SPDX_PRETTY = "1"
235 """
236 )
237 extraconf_hash = hashlib.sha1(EXTRACONF.encode("utf-8")).hexdigest()
238
239 objset = self.check_recipe_spdx(
240 "core-image-minimal",
241 "{DEPLOY_DIR_IMAGE}/core-image-minimal-{MACHINE}.rootfs.spdx.json",
242 # Many SPDX variables do not trigger a rebuild, since they are
243 # intended to record information at the time of the build. As such,
244 # the extra configuration alone may not trigger a rebuild, and even
245 # if it does, the task hash won't necessarily be unique. In order
246 # to make sure rebuilds happen, but still allow these test objects
247 # to be pulled from sstate (e.g. remain reproducible), change the
248 # namespace prefix to include the hash of the extra configuration
249 extraconf=textwrap.dedent(
250 f"""\
251 SPDX_NAMESPACE_PREFIX = "http://spdx.org/spdxdocs/{extraconf_hash}"
252 """
253 )
254 + EXTRACONF,
255 )
256
257 # Document should be fully linked
258 self.check_objset_missing_ids(objset)
259
260 for o in objset.foreach_type(oe.spdx30.SoftwareAgent):
261 if o.name == "CI Tool":
262 break
263 else:
264 self.assertTrue(False, "Unable to find software tool")
265
266 for o in objset.foreach_type(oe.spdx30.Person):
267 if o.name == "John Doe":
268 break
269 else:
270 self.assertTrue(False, "Unable to find person")
271
272 for o in objset.foreach_type(oe.spdx30.Organization):
273 if o.name == "ACME Embedded Widgets":
274 break
275 else:
276 self.assertTrue(False, "Unable to find organization")
277
278 for o in objset.foreach_type(oe.spdx30.SpdxDocument):
279 doc = o
280 break
281 else:
282 self.assertTrue(False, "Unable to find SpdxDocument")
283
284 for i in doc.import_:
285 if i.externalSpdxId == HOST_SPDXID:
286 break
287 else:
288 self.assertTrue(False, "Unable to find imported Host SpdxID")