| #!/usr/bin/env fuchsia-vendored-python | |
| # Copyright 2022 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| import io | |
| import os | |
| import tempfile | |
| import unittest | |
| import assembly | |
| import serialization | |
| from assembly import ( | |
| AIBCreator, | |
| AssemblyInputBundle, | |
| BlobEntry, | |
| FileEntry, | |
| FilePath, | |
| PackageManifest, | |
| PackageMetaData, | |
| SubpackageEntry, | |
| ) | |
| from assembly.assembly_input_bundle import ( | |
| AssemblyInputBundleCreationException, | |
| CompiledComponentDefinition, | |
| CompiledPackageDefinition, | |
| PackagedDriverDetails, | |
| PackageDetails, | |
| ) | |
| from fast_copy_mock import mock_fast_copy_in | |
| raw_package_manifest_json = """{ | |
| "package": { | |
| "name": "some_package", | |
| "version": "42" | |
| }, | |
| "blobs": [ | |
| { | |
| "path": "meta/", | |
| "merkle": "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF", | |
| "size": 4096, | |
| "source_path": "some/source/path/to/a/file" | |
| }, | |
| { | |
| "path": "a/file", | |
| "merkle": "123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF0", | |
| "size": 8192, | |
| "source_path": "some/other/source/path" | |
| }, | |
| { | |
| "path": "an/empty/file", | |
| "merkle": "15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b", | |
| "size": 0, | |
| "source_path": "source/path/to/an/empty/file" | |
| } | |
| ], | |
| "version": "1", | |
| "subpackages": [ | |
| { | |
| "name": "my_subpackage", | |
| "merkle": "23456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF01", | |
| "manifest_path": "subpackages/15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b" | |
| } | |
| ], | |
| "repository": "some_repo" | |
| }""" | |
| empty_blob_raw_json = """{ | |
| "path": "an/empty/file", | |
| "merkle": "15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b", | |
| "size": 0, | |
| "source_path": "source/path/to/an/empty/file" | |
| }""" | |
| class PackageManifestTest(unittest.TestCase): | |
| def test_deserialize_from_json(self) -> None: | |
| manifest = serialization.json_loads( | |
| PackageManifest, raw_package_manifest_json | |
| ) | |
| self.assertEqual( | |
| manifest, | |
| PackageManifest( | |
| PackageMetaData("some_package", 42), | |
| [ | |
| BlobEntry( | |
| path="meta/", | |
| merkle="0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF", | |
| size=4096, | |
| source_path="some/source/path/to/a/file", | |
| ), | |
| BlobEntry( | |
| path="a/file", | |
| merkle="123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF0", | |
| size=8192, | |
| source_path="some/other/source/path", | |
| ), | |
| BlobEntry( | |
| path="an/empty/file", | |
| merkle="15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b", | |
| size=0, | |
| source_path="source/path/to/an/empty/file", | |
| ), | |
| ], | |
| "1", | |
| None, | |
| [ | |
| SubpackageEntry( | |
| name="my_subpackage", | |
| merkle="23456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF01", | |
| manifest_path="subpackages/15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b", | |
| ), | |
| ], | |
| "some_repo", | |
| ), | |
| ) | |
| def test_serialize_json(self) -> None: | |
| manifest = PackageManifest( | |
| PackageMetaData("some_package", 42), | |
| [ | |
| BlobEntry( | |
| path="meta/", | |
| merkle="0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF", | |
| size=4096, | |
| source_path="some/source/path/to/a/file", | |
| ), | |
| BlobEntry( | |
| path="a/file", | |
| merkle="123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF0", | |
| size=8192, | |
| source_path="some/other/source/path", | |
| ), | |
| BlobEntry( | |
| path="an/empty/file", | |
| merkle="15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b", | |
| size=0, | |
| source_path="source/path/to/an/empty/file", | |
| ), | |
| ], | |
| "1", | |
| None, | |
| [ | |
| SubpackageEntry( | |
| name="my_subpackage", | |
| merkle="23456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF01", | |
| manifest_path="subpackages/15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b", | |
| ), | |
| ], | |
| "some_repo", | |
| ) | |
| serialized_json = serialization.json_dumps(manifest, indent=4) | |
| self.maxDiff = None | |
| self.assertEqual(serialized_json, raw_package_manifest_json) | |
| def test_serialize_empty_blob(self) -> None: | |
| blob = BlobEntry( | |
| path="an/empty/file", | |
| merkle="15ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b", | |
| size=0, | |
| source_path="source/path/to/an/empty/file", | |
| ) | |
| self.assertEqual( | |
| serialization.json_dumps(blob, indent=4), empty_blob_raw_json | |
| ) | |
| raw_assembly_input_bundle_json = """{ | |
| "allowed_in": {}, | |
| "scrutiny_required": {}, | |
| "auto_include_in": {}, | |
| "experimental": false, | |
| "kernel": { | |
| "path": "path/to/kernel", | |
| "args": [ | |
| "arg1", | |
| "arg2" | |
| ] | |
| }, | |
| "boot_args": [ | |
| "arg3", | |
| "arg4" | |
| ], | |
| "bootfs_files": [ | |
| { | |
| "source": "path/to/source", | |
| "destination": "path/to/destination" | |
| } | |
| ], | |
| "packages": [ | |
| { | |
| "package": "package0", | |
| "set": "system" | |
| }, | |
| { | |
| "package": "package1", | |
| "set": "base" | |
| }, | |
| { | |
| "package": "package2", | |
| "set": "base" | |
| }, | |
| { | |
| "package": "package3", | |
| "set": "cache" | |
| }, | |
| { | |
| "package": "package4", | |
| "set": "cache" | |
| } | |
| ], | |
| "config_data": { | |
| "package1": [ | |
| { | |
| "source": "path/to/source.json", | |
| "destination": "config.json" | |
| } | |
| ] | |
| }, | |
| "blobs": [], | |
| "drivers": [ | |
| { | |
| "package": "driver_package1", | |
| "set": "base", | |
| "components": [ | |
| "meta/driver_component1.cm", | |
| "meta/driver_component2.cm" | |
| ] | |
| }, | |
| { | |
| "package": "driver_package2", | |
| "set": "base", | |
| "components": [ | |
| "meta/driver2_component1.cm", | |
| "meta/driver2_component2.cm" | |
| ] | |
| } | |
| ], | |
| "bootfs_shell_commands": {}, | |
| "shell_commands": { | |
| "package1": [ | |
| "path/to/binary1", | |
| "path/to/binary2" | |
| ] | |
| }, | |
| "packages_to_compile": [ | |
| { | |
| "name": "foo", | |
| "components": [ | |
| { | |
| "component_name": "bar", | |
| "shards": [ | |
| "baz.cml" | |
| ], | |
| "cmc_features": [] | |
| } | |
| ], | |
| "contents": [], | |
| "includes": [ | |
| "c/d" | |
| ], | |
| "bootfs_package": false | |
| }, | |
| { | |
| "name": "foo", | |
| "components": [ | |
| { | |
| "component_name": "bar", | |
| "shards": [ | |
| "bar/meta.shard.cml" | |
| ], | |
| "cmc_features": [] | |
| } | |
| ], | |
| "contents": [], | |
| "includes": [], | |
| "bootfs_package": false | |
| } | |
| ], | |
| "memory_buckets": [] | |
| }""" | |
| class AssemblyInputBundleTest(unittest.TestCase): | |
| def test_serialization(self) -> None: | |
| self.maxDiff = None | |
| aib = AssemblyInputBundle() | |
| aib.add_packages( | |
| [ | |
| PackageDetails("package1", "base"), | |
| PackageDetails("package2", "base"), | |
| PackageDetails("package3", "cache"), | |
| PackageDetails("package4", "cache"), | |
| PackageDetails("package0", "system"), | |
| ] | |
| ) | |
| aib.kernel.path = "path/to/kernel" | |
| aib.kernel.args.update(["arg1", "arg2"]) | |
| aib.boot_args.update(["arg3", "arg4"]) | |
| aib.bootfs_files.add(FileEntry("path/to/source", "path/to/destination")) | |
| aib.config_data["package1"] = set( | |
| [FileEntry("path/to/source.json", "config.json")] | |
| ) | |
| aib.drivers = [ | |
| PackagedDriverDetails( | |
| "driver_package1", | |
| "base", | |
| set(["meta/driver_component1.cm", "meta/driver_component2.cm"]), | |
| ), | |
| PackagedDriverDetails( | |
| "driver_package2", | |
| "base", | |
| set( | |
| ["meta/driver2_component1.cm", "meta/driver2_component2.cm"] | |
| ), | |
| ), | |
| ] | |
| aib.shell_commands["package1"] = ["path/to/binary1", "path/to/binary2"] | |
| aib.packages_to_compile = [ | |
| CompiledPackageDefinition( | |
| name="foo", | |
| components=[ | |
| CompiledComponentDefinition("bar", set(["baz.cml"])) | |
| ], | |
| includes=set(["c/d"]), | |
| ), | |
| CompiledPackageDefinition( | |
| name="foo", | |
| components=[ | |
| CompiledComponentDefinition( | |
| "bar", set(["bar/meta.shard.cml"]) | |
| ) | |
| ], | |
| ), | |
| ] | |
| self.assertEqual( | |
| serialization.json_dumps(aib, indent=2), | |
| raw_assembly_input_bundle_json, | |
| ) | |
| def test_deserialization(self) -> None: | |
| self.maxDiff = None | |
| aib = AssemblyInputBundle() | |
| aib.add_packages( | |
| [ | |
| PackageDetails("package1", "base"), | |
| PackageDetails("package2", "base"), | |
| PackageDetails("package3", "cache"), | |
| PackageDetails("package4", "cache"), | |
| PackageDetails("package0", "system"), | |
| ] | |
| ) | |
| aib.kernel.path = "path/to/kernel" | |
| aib.kernel.args.update(["arg1", "arg2"]) | |
| aib.boot_args.update(["arg3", "arg4"]) | |
| aib.bootfs_files.add(FileEntry("path/to/source", "path/to/destination")) | |
| aib.config_data["package1"] = set( | |
| [FileEntry("path/to/source.json", "config.json")] | |
| ) | |
| aib.drivers = [ | |
| PackagedDriverDetails( | |
| "driver_package1", | |
| "base", | |
| set(["meta/driver_component1.cm", "meta/driver_component2.cm"]), | |
| ), | |
| PackagedDriverDetails( | |
| "driver_package2", | |
| "base", | |
| set( | |
| ["meta/driver2_component1.cm", "meta/driver2_component2.cm"] | |
| ), | |
| ), | |
| ] | |
| aib.shell_commands["package1"] = ["path/to/binary1", "path/to/binary2"] | |
| aib.packages_to_compile = [ | |
| CompiledPackageDefinition( | |
| name="foo", | |
| components=[ | |
| CompiledComponentDefinition("bar", set(["baz.cml"])) | |
| ], | |
| includes=set(["c/d"]), | |
| ), | |
| CompiledPackageDefinition( | |
| name="foo", | |
| components=[ | |
| CompiledComponentDefinition("bar", set(["bar/meta.cml"])) | |
| ], | |
| ), | |
| ] | |
| parsed_aib = serialization.json_loads( | |
| AssemblyInputBundle, raw_assembly_input_bundle_json | |
| ) | |
| def assert_field_equal( | |
| parsed: AssemblyInputBundle, | |
| expected: AssemblyInputBundle, | |
| field_name: str, | |
| ) -> None: | |
| self.assertEqual( | |
| getattr(parsed, field_name), getattr(expected, field_name) | |
| ) | |
| assert_field_equal(parsed_aib, aib, "packages") | |
| assert_field_equal(parsed_aib, aib, "kernel") | |
| assert_field_equal(parsed_aib, aib, "boot_args") | |
| assert_field_equal(parsed_aib, aib, "bootfs_files") | |
| assert_field_equal(parsed_aib, aib, "config_data") | |
| assert_field_equal(parsed_aib, aib, "drivers") | |
| assert_field_equal(parsed_aib, aib, "shell_commands") | |
| # TODO(https://fxbug.dev/42068124): support deserializing union data types | |
| # assert_field_equal(parsed_aib, aib, "packages_to_compile") | |
| # self.assertEqual(parsed_aib, aib) | |
| def rebase_source(entry: FileEntry, path: str) -> FileEntry: | |
| return FileEntry(os.path.join(path, entry.source), entry.destination) | |
| def rebase_destination(entry: FileEntry, path: str) -> FileEntry: | |
| return FileEntry(entry.source, os.path.join(path, entry.destination)) | |
| def format_merkle(value: int) -> str: | |
| return f"{value:064x}" | |
| class PackageManifestBuilder: | |
| """Used to ease the creation of PackageManfests in tests, in a builder-like | |
| syntax: | |
| PackageManifestBuilder("some name").manifest_path( | |
| "where/to/put/the/manifest").fake_blob(20).blob(path="meta/").build() | |
| Which then creates a package manifest with two blobs, and writes it to the | |
| path given. | |
| """ | |
| def __init__(self, name: str) -> None: | |
| self._manifest_path: FilePath | None = None | |
| self._name = name | |
| self._blobs: list[BlobEntry] = [] | |
| self._name_counter = 0 | |
| def _make_blob_name(self, pattern: str = "blob_") -> str: | |
| name = f"{pattern}{self._name_counter}" | |
| self._name_counter += 1 | |
| return name | |
| def _make_merkle(self, idx: int | None = None) -> str: | |
| if idx is None: | |
| idx = self._name_counter | |
| self._name_counter += 1 | |
| return format_merkle(idx) | |
| def manifest_path(self, path: FilePath) -> "PackageManifestBuilder": | |
| """Set the path that the manifest should be written to.""" | |
| self._manifest_path = path | |
| return self | |
| def blob( | |
| self, | |
| path: str | None = None, | |
| merkle: str | None = None, | |
| size: int | None = None, | |
| source: FilePath | None = None, | |
| ) -> "PackageManifestBuilder": | |
| """Add a blob to the package, creating fake data for any fields that | |
| aren't given. | |
| """ | |
| self._blobs.append( | |
| BlobEntry( | |
| path if path else f"some/path/for/{self._make_blob_name()}", | |
| merkle if merkle else self._make_merkle(), | |
| size if size else 0, | |
| ( | |
| source | |
| if source | |
| else f"source/path/for/{self._make_blob_name('input_')}" | |
| ), | |
| ) | |
| ) | |
| return self | |
| def fake_blob(self, id: int) -> "PackageManifestBuilder": | |
| """Add a completely faked blob to the package, using a numeric id to | |
| provide a way of tracking which package it belongs to. | |
| """ | |
| self._blobs.append( | |
| BlobEntry( | |
| path=f"package/path/for/blob_{id}", | |
| merkle=self._make_merkle(id), | |
| size=id, | |
| source_path=f"source/path/for/input_{id}", | |
| ) | |
| ) | |
| return self | |
| def build(self) -> PackageManifest: | |
| """Construct the PackageManifest object itself, and if a manifest path | |
| was provided, write out the manifest to that location. | |
| """ | |
| manifest = PackageManifest(PackageMetaData(self._name), self._blobs) | |
| if self._manifest_path: | |
| with open(self._manifest_path, "w") as manifest_file: | |
| serialization.json_dump(manifest, manifest_file) | |
| return manifest | |
| class AIBCreatorTest(unittest.TestCase): | |
| def test_aib_creator_file_copy_and_package_manifest_relative_paths( | |
| self, | |
| ) -> None: | |
| """This tests that the AIBCreator will correctly copy the blobs/* files | |
| and create the package manifests in the correct location within the | |
| AIB structure, with package-manifest relative paths to the blobs. | |
| It also tests that the fini manifest and dep-files contain the | |
| correct paths. | |
| """ | |
| # Diffs can be very large for some of these lists, so show the whole | |
| # thing. | |
| self.maxDiff = None | |
| # Mock out the copy routine so it doesn't fail, but we can see the ops | |
| # it would have made. | |
| (_, copies) = mock_fast_copy_in(assembly.package_copier) | |
| outdir = tempfile.TemporaryDirectory() | |
| # save off the current cwd so that it can be restored later. | |
| curr_cwd = os.getcwd() | |
| try: | |
| # Switch into the tempdir to simulate a build environment. | |
| os.chdir(outdir.name) | |
| assembly_dir = "my_assembly" | |
| inputs_dir = "inputs" | |
| os.mkdir(assembly_dir) | |
| os.mkdir(inputs_dir) | |
| # Create two package manifests to use as base packages. These will | |
| # be used to validate that blobs are copied to the right places, and | |
| # that the manifests are re-written correctly in a portable manner. | |
| some_package_manifest_path = "inputs/some_package_manifest.json" | |
| some_package_manifest = ( | |
| PackageManifestBuilder("some_package") | |
| .manifest_path(some_package_manifest_path) | |
| .blob( | |
| path="meta/", | |
| merkle="0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF", | |
| source="some/meta.far", | |
| ) | |
| .fake_blob(11) | |
| .fake_blob(12) | |
| .fake_blob(13) | |
| .build() | |
| ) | |
| another_package_manifest_path = ( | |
| "inputs/another_package_manifest.json" | |
| ) | |
| another_package_manifest = ( | |
| PackageManifestBuilder("another_package") | |
| .manifest_path(another_package_manifest_path) | |
| .blob( | |
| path="meta/", | |
| merkle="123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF0", | |
| source="another/meta.far", | |
| ) | |
| .fake_blob(26) | |
| .fake_blob(27) | |
| .build() | |
| ) | |
| created_manifests = [ | |
| some_package_manifest, | |
| another_package_manifest, | |
| ] | |
| # These are the files, and their sources, that we expect to find | |
| # copied into the AIB. The destination paths here are the paths | |
| # used within the AIB, as that can then be used to compute the path | |
| # as seen from the cwd (which is what needs to be in the copy and | |
| # fini manifest operations). | |
| expected_files = [ | |
| # some_package files | |
| FileEntry( | |
| "some/meta.far", | |
| "blobs/0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF", | |
| ), | |
| FileEntry( | |
| "source/path/for/input_11", f"blobs/{format_merkle(11)}" | |
| ), | |
| FileEntry( | |
| "source/path/for/input_12", f"blobs/{format_merkle(12)}" | |
| ), | |
| FileEntry( | |
| "source/path/for/input_13", f"blobs/{format_merkle(13)}" | |
| ), | |
| # another_package files | |
| FileEntry( | |
| "another/meta.far", | |
| "blobs/123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF0", | |
| ), | |
| FileEntry( | |
| "source/path/for/input_26", f"blobs/{format_merkle(26)}" | |
| ), | |
| FileEntry( | |
| "source/path/for/input_27", f"blobs/{format_merkle(27)}" | |
| ), | |
| ] | |
| source_package_manifests = [ | |
| some_package_manifest_path, | |
| another_package_manifest_path, | |
| ] | |
| expected_package_manifests = [ | |
| PackageDetails("packages/some_package", "base"), | |
| PackageDetails("packages/another_package", "base"), | |
| ] | |
| # Create the AIBCreator and perform the operation that's under test. | |
| aib_creator = AIBCreator(assembly_dir) | |
| aib_creator.packages.update( | |
| [ | |
| PackageDetails(some_package_manifest_path, "base"), | |
| PackageDetails(another_package_manifest_path, "base"), | |
| ] | |
| ) | |
| bundle, bundle_path, deps = aib_creator.build() | |
| # Verify that the bundle was written to the correct location (and it | |
| # matches the returned bundle). | |
| self.assertEqual( | |
| bundle_path, os.path.join(assembly_dir, "assembly_config.json") | |
| ) | |
| with open(bundle_path) as bundle_file: | |
| parsed_bundle = serialization.json_load( | |
| AssemblyInputBundle, bundle_file | |
| ) | |
| self.assertEqual(parsed_bundle, bundle) | |
| # Verify that resultant AIB contains the correct base packages. | |
| self.assertEqual(bundle.packages, set(expected_package_manifests)) | |
| # Verify that the package manfiests have been rewritten to use file- | |
| # relative blob paths into the correct directory. | |
| def validate_rewritten_package_manifest( | |
| path: FilePath, expected: PackageManifest | |
| ) -> None: | |
| """Parses the PackageManifest at the given path, and compares it | |
| with the `expected` one. | |
| """ | |
| with open(path) as package_manifest_file: | |
| parsed_manifest = serialization.json_load( | |
| PackageManifest, package_manifest_file | |
| ) | |
| self.assertEqual(parsed_manifest.package, expected.package) | |
| self.assertEqual(parsed_manifest.blob_sources_relative, "file") | |
| # The expected blobs are the passed-in set, but with the source | |
| # path set to be by merkle in the blobs/ dir of the AIB. | |
| expected_blobs = [ | |
| BlobEntry( | |
| blob.path, | |
| blob.merkle, | |
| blob.size, | |
| f"../blobs/{blob.merkle}", | |
| ) | |
| for blob in expected.blobs | |
| ] | |
| self.assertEqual(parsed_manifest.blobs, expected_blobs) | |
| for package_details, manifest in zip( | |
| expected_package_manifests, created_manifests | |
| ): | |
| validate_rewritten_package_manifest( | |
| os.path.join(assembly_dir, package_details.package), | |
| manifest, | |
| ) | |
| # Verify that the copies that were performed by the mocked | |
| # `fast_copy()` fn are correct. | |
| # | |
| # The expected copies are the the expected_files, but the | |
| # destination path is rebased to the cwd (prepending `assembly_dir`) | |
| # | |
| expected_copies = [ | |
| rebase_destination(entry, assembly_dir) | |
| for entry in expected_files | |
| ] | |
| self.assertEqual(sorted(copies), sorted(expected_copies)) | |
| # Verify that the deps are correctly reported. | |
| # | |
| # All the source paths that files were copied from should be listed, | |
| # as well as the package manifest paths that were read to create the | |
| # in-AIB version of the manifests. | |
| expected_deps = [entry.source for entry in expected_files] | |
| expected_deps.extend(source_package_manifests) | |
| self.assertEqual(sorted(deps), sorted(expected_deps)) | |
| # Verify that the fini manifest created (used to create archives of | |
| # the AIB is correct). | |
| # | |
| # The fini manifest contains all the destination paths from | |
| # expected_files, for both source and destination, but assembly_dir | |
| # is prepended to all source paths to make them relative to the cwd. | |
| # Verify that all_file_paths() returns the correct (AIB-relative) | |
| # files. | |
| expected_paths = [entry.destination for entry in expected_files] | |
| expected_paths.extend( | |
| [p.package for p in expected_package_manifests] | |
| ) | |
| self.assertEqual( | |
| sorted(bundle.all_file_paths()), sorted(expected_paths) | |
| ) | |
| # Created the expected entries from the expected_paths by pre- | |
| # pending the assembly_dir to create the source path. | |
| expected_paths.append("assembly_config.json") | |
| expected_fini_contents = sorted( | |
| [f"{path}={assembly_dir}/{path}" for path in expected_paths] | |
| ) | |
| # Write the fini manifest to a string buffer | |
| fini_file = io.StringIO() | |
| bundle.write_fini_manifest(fini_file, base_dir=assembly_dir) | |
| # Parse the written buffer into lines to compare with the expected | |
| # entries. | |
| fini_entries = sorted(fini_file.getvalue().splitlines()) | |
| self.assertEqual(fini_entries, expected_fini_contents) | |
| finally: | |
| os.chdir(curr_cwd) | |
| # Clean up the tempdir | |
| outdir.cleanup() | |
| class AIBCreatorIncludeRulesTest(unittest.TestCase): | |
| def test_add_allowed_in_everything(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| creator.add_allowed_in("everything") | |
| self.assertEqual( | |
| creator.allowed_in, | |
| { | |
| "standard": {"user", "userdebug", "eng"}, | |
| "utility": {"user", "userdebug", "eng"}, | |
| "bootstrap": {"user", "userdebug", "eng"}, | |
| "embeddable": {"user", "userdebug", "eng"}, | |
| }, | |
| ) | |
| def test_add_allowed_in_feature_set(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| creator.add_allowed_in("standard") | |
| self.assertEqual( | |
| creator.allowed_in, | |
| { | |
| "standard": {"user", "userdebug", "eng"}, | |
| }, | |
| ) | |
| def test_add_allowed_in_build_type(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| creator.add_allowed_in("eng") | |
| self.assertEqual( | |
| creator.allowed_in, | |
| { | |
| "standard": {"eng"}, | |
| "utility": {"eng"}, | |
| "bootstrap": {"eng"}, | |
| "embeddable": {"eng"}, | |
| }, | |
| ) | |
| def test_add_allowed_in_specific(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| creator.add_allowed_in("standard::eng") | |
| self.assertEqual( | |
| creator.allowed_in, | |
| { | |
| "standard": {"eng"}, | |
| }, | |
| ) | |
| def test_add_scrutiny_required_mix(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| creator.add_scrutiny_required("standard::eng") | |
| creator.add_scrutiny_required("utility") | |
| self.assertEqual( | |
| creator.scrutiny_required, | |
| { | |
| "standard": {"eng"}, | |
| "utility": {"user", "userdebug", "eng"}, | |
| }, | |
| ) | |
| def test_invalid_rule_format(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| with self.assertRaises(ValueError): | |
| creator.add_allowed_in("invalid") | |
| def test_invalid_feature_set(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| with self.assertRaises(AssertionError): | |
| creator.add_allowed_in("invalid::eng") | |
| def test_invalid_build_type(self) -> None: | |
| creator = AIBCreator(tempfile.mkdtemp()) | |
| with self.assertRaises(AssertionError): | |
| creator.add_allowed_in("standard::invalid") | |
| def test_auto_include_mutual_exclusivity(self) -> None: | |
| # Create a temp dir for validation | |
| outdir = tempfile.TemporaryDirectory() | |
| try: | |
| creator = AIBCreator(outdir.name) | |
| creator.add_allowed_in("standard::eng") | |
| creator.add_auto_include_in("utility::user") | |
| # This should fail because allowed_in and auto_include_in are mutually exclusive | |
| # from assembly.assembly_input_bundle import AssemblyInputBundleCreationException | |
| with self.assertRaises(AssemblyInputBundleCreationException): | |
| creator.build() | |
| finally: | |
| outdir.cleanup() | |
| def test_auto_include_mutual_exclusivity_scrutiny_required(self) -> None: | |
| # Create a temp dir for validation | |
| outdir = tempfile.TemporaryDirectory() | |
| try: | |
| creator = AIBCreator(outdir.name) | |
| creator.add_scrutiny_required("standard::eng") | |
| creator.add_auto_include_in("utility::user") | |
| # This should fail because scrutiny_required and auto_include_in are mutually exclusive | |
| with self.assertRaises(AssemblyInputBundleCreationException): | |
| creator.build() | |
| finally: | |
| outdir.cleanup() | |
| def test_experimental_fails_on_user(self) -> None: | |
| outdir = tempfile.TemporaryDirectory() | |
| try: | |
| creator = AIBCreator(outdir.name, experimental=True) | |
| creator.add_allowed_in("standard::user") | |
| with self.assertRaises(AssemblyInputBundleCreationException): | |
| creator.build() | |
| finally: | |
| outdir.cleanup() | |
| def test_testonly_fails_on_user(self) -> None: | |
| outdir = tempfile.TemporaryDirectory() | |
| try: | |
| creator = AIBCreator(outdir.name, testonly=True) | |
| with self.assertRaises(ValueError): | |
| creator.add_allowed_in("standard::user") | |
| finally: | |
| outdir.cleanup() |