| # 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() |