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 36 37class SBOMWritersTest(unittest.TestCase): 38 39 def setUp(self): 40 # SBOM of a product 41 self.sbom_doc = sbom_data.Document(name='test doc', 42 namespace='http://www.google.com/sbom/spdx/android', 43 creators=[SUPPLIER_GOOGLE], 44 created='2023-03-31T22:17:58Z', 45 describes=sbom_data.SPDXID_PRODUCT) 46 self.sbom_doc.add_external_ref( 47 sbom_data.DocumentExternalReference(id='DocumentRef-external_doc_ref', 48 uri='external_doc_uri', 49 checksum='SHA1: 1234567890')) 50 self.sbom_doc.add_package( 51 sbom_data.Package(id=sbom_data.SPDXID_PRODUCT, 52 name=sbom_data.PACKAGE_NAME_PRODUCT, 53 download_location=sbom_data.VALUE_NONE, 54 supplier=SUPPLIER_GOOGLE, 55 version=BUILD_FINGER_PRINT, 56 files_analyzed=True, 57 verification_code='123456', 58 file_ids=[SPDXID_FILE1, SPDXID_FILE2, SPDXID_FILE3])) 59 60 self.sbom_doc.add_package( 61 sbom_data.Package(id=sbom_data.SPDXID_PLATFORM, 62 name=sbom_data.PACKAGE_NAME_PLATFORM, 63 download_location=sbom_data.VALUE_NONE, 64 supplier=SUPPLIER_GOOGLE, 65 version=BUILD_FINGER_PRINT, 66 )) 67 68 self.sbom_doc.add_package( 69 sbom_data.Package(id=SPDXID_PREBUILT_PACKAGE1, 70 name='Prebuilt package1', 71 download_location=sbom_data.VALUE_NONE, 72 supplier=SUPPLIER_GOOGLE, 73 version=BUILD_FINGER_PRINT, 74 )) 75 76 self.sbom_doc.add_package( 77 sbom_data.Package(id=SPDXID_SOURCE_PACKAGE1, 78 name='Source package1', 79 download_location=sbom_data.VALUE_NONE, 80 supplier=SUPPLIER_GOOGLE, 81 version=BUILD_FINGER_PRINT, 82 external_refs=[sbom_data.PackageExternalRef( 83 category=sbom_data.PackageExternalRefCategory.SECURITY, 84 type=sbom_data.PackageExternalRefType.cpe22Type, 85 locator='cpe:/a:jsoncpp_project:jsoncpp:1.9.4')] 86 )) 87 88 self.sbom_doc.add_package( 89 sbom_data.Package(id=SPDXID_UPSTREAM_PACKAGE1, 90 name='Upstream package1', 91 supplier=SUPPLIER_UPSTREAM, 92 version='1.1', 93 )) 94 95 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_SOURCE_PACKAGE1, 96 relationship=sbom_data.RelationshipType.VARIANT_OF, 97 id2=SPDXID_UPSTREAM_PACKAGE1)) 98 99 self.sbom_doc.files.append( 100 sbom_data.File(id=SPDXID_FILE1, name='/bin/file1', checksum='SHA1: 11111')) 101 self.sbom_doc.files.append( 102 sbom_data.File(id=SPDXID_FILE2, name='/bin/file2', checksum='SHA1: 22222')) 103 self.sbom_doc.files.append( 104 sbom_data.File(id=SPDXID_FILE3, name='/bin/file3', checksum='SHA1: 33333')) 105 self.sbom_doc.files.append( 106 sbom_data.File(id=SPDXID_FILE4, name='file4.a', checksum='SHA1: 44444')) 107 108 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 109 relationship=sbom_data.RelationshipType.GENERATED_FROM, 110 id2=sbom_data.SPDXID_PLATFORM)) 111 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE2, 112 relationship=sbom_data.RelationshipType.GENERATED_FROM, 113 id2=SPDXID_PREBUILT_PACKAGE1)) 114 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE3, 115 relationship=sbom_data.RelationshipType.GENERATED_FROM, 116 id2=SPDXID_SOURCE_PACKAGE1 117 )) 118 self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 119 relationship=sbom_data.RelationshipType.STATIC_LINK, 120 id2=SPDXID_FILE4 121 )) 122 123 # SBOM fragment of a APK 124 self.unbundled_sbom_doc = sbom_data.Document(name='test doc', 125 namespace='http://www.google.com/sbom/spdx/android', 126 creators=[SUPPLIER_GOOGLE], 127 created='2023-03-31T22:17:58Z', 128 describes=SPDXID_FILE1) 129 130 self.unbundled_sbom_doc.files.append( 131 sbom_data.File(id=SPDXID_FILE1, name='/bin/file1.apk', checksum='SHA1: 11111')) 132 self.unbundled_sbom_doc.add_package( 133 sbom_data.Package(id=SPDXID_SOURCE_PACKAGE1, 134 name='Unbundled apk package', 135 download_location=sbom_data.VALUE_NONE, 136 supplier=SUPPLIER_GOOGLE, 137 version=BUILD_FINGER_PRINT)) 138 self.unbundled_sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 139 relationship=sbom_data.RelationshipType.GENERATED_FROM, 140 id2=SPDXID_SOURCE_PACKAGE1)) 141 142 def test_tagvalue_writer(self): 143 with io.StringIO() as output: 144 sbom_writers.TagValueWriter.write(self.sbom_doc, output) 145 expected_output = pathlib.Path('testdata/expected_tagvalue_sbom.spdx').read_text() 146 self.maxDiff = None 147 self.assertEqual(expected_output, output.getvalue()) 148 149 def test_tagvalue_writer_doc_describes_file(self): 150 with io.StringIO() as output: 151 self.sbom_doc.describes = SPDXID_FILE4 152 sbom_writers.TagValueWriter.write(self.sbom_doc, output) 153 expected_output = pathlib.Path('testdata/expected_tagvalue_sbom_doc_describes_file.spdx').read_text() 154 self.maxDiff = None 155 self.assertEqual(expected_output, output.getvalue()) 156 157 def test_tagvalue_writer_unbundled(self): 158 with io.StringIO() as output: 159 sbom_writers.TagValueWriter.write(self.unbundled_sbom_doc, output, fragment=True) 160 expected_output = pathlib.Path('testdata/expected_tagvalue_sbom_unbundled.spdx').read_text() 161 self.maxDiff = None 162 self.assertEqual(expected_output, output.getvalue()) 163 164 def test_json_writer(self): 165 with io.StringIO() as output: 166 sbom_writers.JSONWriter.write(self.sbom_doc, output) 167 expected_output = pathlib.Path('testdata/expected_json_sbom.spdx.json').read_text() 168 self.maxDiff = None 169 self.assertEqual(expected_output, output.getvalue()) 170 171 172if __name__ == '__main__': 173 unittest.main(verbosity=2) 174