|  | # Copyright lowRISC contributors. | 
|  | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | import os | 
|  | import unittest | 
|  | import unittest.mock | 
|  |  | 
|  | from bitstreams_workspace import BitstreamCache | 
|  |  | 
|  |  | 
|  | class TestBitstreamCache(unittest.TestCase): | 
|  |  | 
|  | def test_make_with_default(self): | 
|  | # Changes to command-line argument defaults could break this method, so | 
|  | # it's important to at least have code coverage. | 
|  | BitstreamCache.MakeWithDefaults() | 
|  |  | 
|  | def test_get_from_cache(self): | 
|  | BITSTREAM_ORIG = 'lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig' | 
|  | BITSTREAM_SPLICE = 'lowrisc_systems_chip_earlgrey_cw310_0.1.bit.splice' | 
|  |  | 
|  | MOCKED_OS_WALK_RETURN = [ | 
|  | # os.walk() yields tuples of the form (root, dir, files). | 
|  | ('cache/abcd', [], | 
|  | [BITSTREAM_ORIG, BITSTREAM_SPLICE, 'rom.mmi', 'otp.mmi']), | 
|  | ] | 
|  | os.walk = unittest.mock.MagicMock(name='os.walk', | 
|  | return_value=MOCKED_OS_WALK_RETURN) | 
|  |  | 
|  | cache = BitstreamCache('/', | 
|  | '/tmp/cache/opentitan-bitstreams', | 
|  | 'latest.txt', | 
|  | offline=True) | 
|  | cache.InitRepository = unittest.mock.MagicMock(name='method') | 
|  |  | 
|  | cached_files = cache.GetFromCache('abcd') | 
|  |  | 
|  | # This is more of an implementation detail, but it verifies that we hit | 
|  | # the the mocked `os.walk` function as expected. | 
|  | os.walk.assert_called_once_with('cache/abcd') | 
|  |  | 
|  | self.assertEqual( | 
|  | dict(cached_files), { | 
|  | "schemaVersion": 1, | 
|  | "buildId": "abcd", | 
|  | "outputFiles": { | 
|  | BITSTREAM_ORIG: { | 
|  | "buildTarget": "//hw/bitstream/vivado:fpga_cw310", | 
|  | "outputInfo": { | 
|  | "@type": "bitstreamInfo", | 
|  | "design": "chip_earlgrey_cw310" | 
|  | } | 
|  | }, | 
|  | "rom.mmi": { | 
|  | "buildTarget": "//hw/bitstream/vivado:fpga_cw310", | 
|  | "outputInfo": { | 
|  | "@type": "memoryMapInfo", | 
|  | "design": "chip_earlgrey_cw310", | 
|  | "memoryId": "rom", | 
|  | } | 
|  | }, | 
|  | "otp.mmi": { | 
|  | "buildTarget": "//hw/bitstream/vivado:fpga_cw310", | 
|  | "outputInfo": { | 
|  | "@type": "memoryMapInfo", | 
|  | "design": "chip_earlgrey_cw310", | 
|  | "memoryId": "otp", | 
|  | } | 
|  | }, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | os.walk.assert_called_once_with('cache/abcd') | 
|  |  | 
|  | def test_write_build_file(self): | 
|  | BITSTREAM_ORIG = 'lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig' | 
|  | BITSTREAM_SPLICE = 'lowrisc_systems_chip_earlgrey_cw310_0.1.bit.splice' | 
|  |  | 
|  | MOCKED_OS_WALK_RETURN = [ | 
|  | # os.walk() yields tuples of the form (root, dir, files). | 
|  | ('cache/abcd', [], | 
|  | [BITSTREAM_ORIG, BITSTREAM_SPLICE, 'rom.mmi', 'otp.mmi']), | 
|  | ] | 
|  | os.walk = unittest.mock.MagicMock(name='os.walk', | 
|  | return_value=MOCKED_OS_WALK_RETURN) | 
|  |  | 
|  | BitstreamCache._GetDateTimeStr = unittest.mock.MagicMock( | 
|  | name='BitstreamCache._GetDateTimeStr', | 
|  | return_value='2022-07-14T15:02:54.463801') | 
|  |  | 
|  | cache = BitstreamCache('/', | 
|  | '/tmp/cache/opentitan-bitstreams', | 
|  | 'latest.txt', | 
|  | offline=True) | 
|  | cache.InitRepository = unittest.mock.MagicMock(name='method') | 
|  |  | 
|  | bazel_string = cache._ConstructBazelString('BUILD.mock', 'abcd') | 
|  | self.maxDiff = None | 
|  | self.assertEqual( | 
|  | bazel_string, '''# This file was autogenerated. Do not edit! | 
|  | # Built at 2022-07-14T15:02:54.463801. | 
|  | # Configured for bitstream: abcd | 
|  |  | 
|  | package(default_visibility = ["//visibility:public"]) | 
|  |  | 
|  | exports_files(glob(["cache/**"])) | 
|  |  | 
|  | filegroup( | 
|  | name = "chip_earlgrey_cw310_bitstream", | 
|  | srcs = ["cache/abcd/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig"], | 
|  | ) | 
|  |  | 
|  | filegroup( | 
|  | name = "chip_earlgrey_cw310_otp_mmi", | 
|  | srcs = ["cache/abcd/otp.mmi"], | 
|  | ) | 
|  |  | 
|  | filegroup( | 
|  | name = "chip_earlgrey_cw310_rom_mmi", | 
|  | srcs = ["cache/abcd/rom.mmi"], | 
|  | ) | 
|  | ''') | 
|  |  | 
|  | # This is more of an implementation detail, but it verifies that we hit | 
|  | # the the mocked `os.walk` function as expected. | 
|  | os.walk.assert_called_once_with('cache/abcd') | 
|  |  | 
|  |  | 
|  | class TestFetchAvailableBitstreams(unittest.TestCase): | 
|  | """ | 
|  | The BitstreamCache.GetBitstreamsAvailable method calls the XML API on | 
|  | the root level of the GCP bucket to get the list of files in the | 
|  | bitstream cache. If this list is sufficiently long, the response will | 
|  | be paginated, as indicated by the <IsTruncated> tag. For p pages, | 
|  | GetBitstreamsAvailable is expected to make p+1 calls to the API (using | 
|  | BitstreamCache.Get). The last call is to get latest.txt which indicates | 
|  | the latest bitstream. | 
|  |  | 
|  | The XML response elements are documented here: | 
|  | https://cloud.google.com/storage/docs/xml-api/get-bucket-list#response_body_elements | 
|  | """ | 
|  |  | 
|  | def setUp(self): | 
|  | self.cache = BitstreamCache('/', '/tmp/cache/opentitan-bitstreams', | 
|  | 'latest.txt') | 
|  | self.cache.InitRepository = unittest.mock.MagicMock(name='method') | 
|  |  | 
|  | def test_fetch_available_bitstreams_single_page(self): | 
|  | """Test fetching the available bitstreams without pagination.""" | 
|  |  | 
|  | MOCKED_GET_RETURN = [ | 
|  | b"""<ListBucketResult xmlns="http://doc.s3.amazonaws.com/2006-03-01"> | 
|  | <Name>opentitan-bitstreams</Name> | 
|  | <Prefix/> | 
|  | <Marker/> | 
|  | <IsTruncated>false</IsTruncated> | 
|  | <Contents> | 
|  | <Key>master/bitstream-0.tar.gz</Key> | 
|  | <Generation>1669083850593267</Generation> | 
|  | <MetaGeneration>1</MetaGeneration> | 
|  | <LastModified>2022-11-22T02:24:10.633Z</LastModified> | 
|  | <ETag>"e82b93a0f88161e594ef79f41277de92"</ETag> | 
|  | <Size>18845320</Size> | 
|  | </Contents> | 
|  | </ListBucketResult>""", b"""2022-12-01T14:54:13 | 
|  | 0""" | 
|  | ] | 
|  |  | 
|  | # Mock out the Get method to simulate a network access | 
|  | self.cache.Get = unittest.mock.MagicMock( | 
|  | name='cache.Get', | 
|  | side_effect=MOCKED_GET_RETURN, | 
|  | ) | 
|  | self.cache.GetBitstreamsAvailable(refresh=True) | 
|  | self.assertEqual(self.cache.Get.call_count, 2) | 
|  | self.assertEqual(self.cache.available, { | 
|  | "0": "master/bitstream-0.tar.gz", | 
|  | "latest": "0", | 
|  | }) | 
|  |  | 
|  | def test_fetch_available_bitstreams_with_pagination(self): | 
|  | """Test fetching the XML file with the list of available bitstreams.""" | 
|  | MOCKED_GET_RETURN = [ | 
|  | b"""<ListBucketResult xmlns="http://doc.s3.amazonaws.com/2006-03-01"> | 
|  | <Name>opentitan-bitstreams</Name> | 
|  | <Prefix/> | 
|  | <Marker/> | 
|  | <NextMarker>master/bitstream-1.tar.gz</NextMarker> | 
|  | <IsTruncated>true</IsTruncated> | 
|  | <Contents> | 
|  | <Key>master/bitstream-0.tar.gz</Key> | 
|  | <Generation>1656382040594268</Generation> | 
|  | <MetaGeneration>1</MetaGeneration> | 
|  | <LastModified>2022-06-28T02:07:20.635Z</LastModified> | 
|  | <ETag>"5b6f3f9ef43f67b988cac31c73949e85"</ETag> | 
|  | <Size>12254300</Size> | 
|  | </Contents> | 
|  | </ListBucketResult>""", | 
|  | b"""<ListBucketResult xmlns="http://doc.s3.amazonaws.com/2006-03-01"> | 
|  | <Name>opentitan-bitstreams</Name> | 
|  | <Prefix/> | 
|  | <Marker>master/bitstream-1.tar.gz</Marker> | 
|  | <IsTruncated>false</IsTruncated> | 
|  | <Contents> | 
|  | <Key>master/bitstream-1.tar.gz</Key> | 
|  | <Generation>1656382040594268</Generation> | 
|  | <MetaGeneration>1</MetaGeneration> | 
|  | <LastModified>2022-06-28T02:07:20.635Z</LastModified> | 
|  | <ETag>"5b6f3f9ef43f67b988cac31c73949e85"</ETag> | 
|  | <Size>12254300</Size> | 
|  | </Contents> | 
|  | <Contents> | 
|  | <Key>master/latest.txt</Key> | 
|  | <Generation>1669836798495359</Generation> | 
|  | <MetaGeneration>1</MetaGeneration> | 
|  | <LastModified>2022-11-30T19:33:18.615Z</LastModified> | 
|  | <ETag>"58498757ff6f02bcbfbae67eb92dfa4b"</ETag> | 
|  | <Size>60</Size> | 
|  | </Contents> | 
|  | </ListBucketResult> | 
|  | """, b"""2022-12-01T14:54:13 | 
|  | 1""" | 
|  | ] | 
|  |  | 
|  | # Mock out the Get method to simulate a network access | 
|  | self.cache.Get = unittest.mock.MagicMock( | 
|  | name='cache.Get', | 
|  | side_effect=MOCKED_GET_RETURN, | 
|  | ) | 
|  | self.cache.GetBitstreamsAvailable(refresh=True) | 
|  | self.assertEqual(self.cache.Get.call_count, 3) | 
|  | self.assertEqual( | 
|  | self.cache.available, { | 
|  | "0": "master/bitstream-0.tar.gz", | 
|  | "1": "master/bitstream-1.tar.gz", | 
|  | "latest": "1", | 
|  | }) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | unittest.main() |