1#!/usr/bin/env python3 2# 3# Copyright (C) 2023 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import io 18import pathlib 19import unittest 20import sbom_data 21import sbom_writers 22 23BUILD_FINGER_PRINT = 'build_finger_print' 24SUPPLIER_GOOGLE = 'Organization: Google' 25SUPPLIER_UPSTREAM = 'Organization: upstream' 26 27SPDXID_PREBUILT_PACKAGE1 = 'SPDXRef-PREBUILT-package1' 28SPDXID_SOURCE_PACKAGE1 = 'SPDXRef-SOURCE-package1' 29SPDXID_UPSTREAM_PACKAGE1 = 'SPDXRef-UPSTREAM-package1' 30 31SPDXID_FILE1 = 'SPDXRef-file1' 32SPDXID_FILE2 = 'SPDXRef-file2' 33SPDXID_FILE3 = 'SPDXRef-file3' 34SPDXID_FILE4 = 'SPDXRef-file4' 35 36SPDXID_LICENSE_1 = 'LicenseRef-Android-License-1' 37SPDXID_LICENSE_2 = 'LicenseRef-Android-License-2' 38SPDXID_LICENSE_3 = 'LicenseRef-Android-License-3' 39 40LICENSE_APACHE_TEXT = "LICENSE_APACHE" 41LICENSE1_TEXT = 'LICENSE 1' 42LICENSE2_TEXT = 'LICENSE 2' 43LICENSE3_TEXT = 'LICENSE 3' 44 45class SBOMWritersTest(unittest.TestCase): 46 47 def setUp(self): 48 # SBOM of a product 49 self.sbom_doc = sbom_data.Document(name='test doc', 50 namespace='http://www.google.com/sbom/spdx/android', 51 creators=[SUPPLIER_GOOGLE], 52 created='2023-03-31T22:17:58Z', 53 describes=sbom_data.SPDXID_PRODUCT) 54 self.sbom_doc.add_external_ref( 55 sbom_data.DocumentExternalReference(id='DocumentRef-external_doc_ref', 56 uri='external_doc_uri', 57 checksum='SHA1: 1234567890')) 58 self.sbom_doc.add_package( 59 sbom_data.Package(id=sbom_data.SPDXID_PRODUCT, 60 name=sbom_data.PACKAGE_NAME_PRODUCT, 61 download_location=sbom_data.VALUE_NONE, 62 supplier=SUPPLIER_GOOGLE, 63 version=BUILD_FINGER_PRINT, 64 files_analyzed=True, 65 verification_code='123456', 66 file_ids=[SPDXID_FILE1, SPDXID_FILE2, SPDXID_FILE3])) 67 68 self.sbom_doc.add_package( 69 sbom_data.Package(id=sbom_data.SPDXID_PLATFORM, 70 name=sbom_data.PACKAGE_NAME_PLATFORM, 71 download_location=sbom_data.VALUE_NONE, 72 supplier=SUPPLIER_GOOGLE, 73 version=BUILD_FINGER_PRINT, 74 declared_license_ids=[sbom_data.SPDXID_LICENSE_APACHE] 75 )) 76 77 self.sbom_doc.add_package( 78 sbom_data.Package(id=SPDXID_PREBUILT_PACKAGE1, 79 name='Prebuilt package1', 80 download_location=sbom_data.VALUE_NONE, 81 supplier=SUPPLIER_GOOGLE, 82 version=BUILD_FINGER_PRINT, 83 declared_license_ids=[SPDXID_LICENSE_1], 84 )) 85 86 self.sbom_doc.add_package( 87 sbom_data.Package(id=SPDXID_SOURCE_PACKAGE1, 88 name='Source package1', 89 download_location=sbom_data.VALUE_NONE, 90 supplier=SUPPLIER_GOOGLE, 91 version=BUILD_FINGER_PRINT, 92 declared_license_ids=[SPDXID_LICENSE_2, SPDXID_LICENSE_3], 93 external_refs=[sbom_data.PackageExternalRef( 94 category=sbom_data.PackageExternalRefCategory.SECURITY, 95 type=sbom_data.PackageExternalRefType.cpe22Type, 96 locator='cpe:/a:jsoncpp_project:jsoncpp:1.9.4')] 97 )) 98 99 self.sbom_doc.add_package( 100 sbom_data.Package(id=SPDXID_UPSTREAM_PACKAGE1, 101 name='Upstream package1', 102 supplier=SUPPLIER_UPSTREAM, 103 version='1.1', 104 declared_license_ids=[SPDXID_LICENSE_2, SPDXID_LICENSE_3], 105 )) 106 107 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_SOURCE_PACKAGE1, 108 relationship=sbom_data.RelationshipType.VARIANT_OF, 109 id2=SPDXID_UPSTREAM_PACKAGE1)) 110 111 self.sbom_doc.files.append( 112 sbom_data.File(id=SPDXID_FILE1, name='/bin/file1', checksum='SHA1: 11111', concluded_license_ids=[sbom_data.SPDXID_LICENSE_APACHE])) 113 self.sbom_doc.files.append( 114 sbom_data.File(id=SPDXID_FILE2, name='/bin/file2', checksum='SHA1: 22222', concluded_license_ids=[SPDXID_LICENSE_1])) 115 self.sbom_doc.files.append( 116 sbom_data.File(id=SPDXID_FILE3, name='/bin/file3', checksum='SHA1: 33333', concluded_license_ids=[SPDXID_LICENSE_2, SPDXID_LICENSE_3])) 117 self.sbom_doc.files.append( 118 sbom_data.File(id=SPDXID_FILE4, name='file4.a', checksum='SHA1: 44444')) 119 120 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 121 relationship=sbom_data.RelationshipType.GENERATED_FROM, 122 id2=sbom_data.SPDXID_PLATFORM)) 123 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE2, 124 relationship=sbom_data.RelationshipType.GENERATED_FROM, 125 id2=SPDXID_PREBUILT_PACKAGE1)) 126 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE3, 127 relationship=sbom_data.RelationshipType.GENERATED_FROM, 128 id2=SPDXID_SOURCE_PACKAGE1 129 )) 130 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 131 relationship=sbom_data.RelationshipType.STATIC_LINK, 132 id2=SPDXID_FILE4 133 )) 134 135 self.sbom_doc.add_license(sbom_data.License(sbom_data.SPDXID_LICENSE_APACHE, LICENSE_APACHE_TEXT, "License-Apache")) 136 self.sbom_doc.add_license(sbom_data.License(SPDXID_LICENSE_1, LICENSE1_TEXT, "License-1")) 137 self.sbom_doc.add_license(sbom_data.License(SPDXID_LICENSE_2, LICENSE2_TEXT, "License-2")) 138 self.sbom_doc.add_license(sbom_data.License(SPDXID_LICENSE_3, LICENSE3_TEXT, "License-3")) 139 140 # SBOM fragment of a APK 141 self.unbundled_sbom_doc = sbom_data.Document(name='test doc', 142 namespace='http://www.google.com/sbom/spdx/android', 143 creators=[SUPPLIER_GOOGLE], 144 created='2023-03-31T22:17:58Z', 145 describes=SPDXID_FILE1) 146 147 self.unbundled_sbom_doc.files.append( 148 sbom_data.File(id=SPDXID_FILE1, name='/bin/file1.apk', checksum='SHA1: 11111')) 149 self.unbundled_sbom_doc.add_package( 150 sbom_data.Package(id=SPDXID_SOURCE_PACKAGE1, 151 name='Unbundled apk package', 152 download_location=sbom_data.VALUE_NONE, 153 supplier=SUPPLIER_GOOGLE, 154 version=BUILD_FINGER_PRINT)) 155 self.unbundled_sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 156 relationship=sbom_data.RelationshipType.GENERATED_FROM, 157 id2=SPDXID_SOURCE_PACKAGE1)) 158 159 def test_tagvalue_writer(self): 160 with io.StringIO() as output: 161 sbom_writers.TagValueWriter.write(self.sbom_doc, output) 162 expected_output = pathlib.Path('testdata/expected_tagvalue_sbom.spdx').read_text() 163 self.maxDiff = None 164 self.assertEqual(expected_output, output.getvalue()) 165 166 def test_tagvalue_writer_doc_describes_file(self): 167 with io.StringIO() as output: 168 self.sbom_doc.describes = SPDXID_FILE4 169 sbom_writers.TagValueWriter.write(self.sbom_doc, output) 170 expected_output = pathlib.Path('testdata/expected_tagvalue_sbom_doc_describes_file.spdx').read_text() 171 self.maxDiff = None 172 self.assertEqual(expected_output, output.getvalue()) 173 174 def test_tagvalue_writer_unbundled(self): 175 with io.StringIO() as output: 176 sbom_writers.TagValueWriter.write(self.unbundled_sbom_doc, output, fragment=True) 177 expected_output = pathlib.Path('testdata/expected_tagvalue_sbom_unbundled.spdx').read_text() 178 self.maxDiff = None 179 self.assertEqual(expected_output, output.getvalue()) 180 181 def test_json_writer(self): 182 with io.StringIO() as output: 183 sbom_writers.JSONWriter.write(self.sbom_doc, output) 184 expected_output = pathlib.Path('testdata/expected_json_sbom.spdx.json').read_text() 185 self.maxDiff = None 186 self.assertEqual(expected_output, output.getvalue()) 187 188 189if __name__ == '__main__': 190 unittest.main(verbosity=2) 191