| #!/usr/bin/env fuchsia-vendored-python | |
| # Copyright 2026 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. | |
| """Unit tests for android_misc_info.py.""" | |
| import json | |
| import pathlib | |
| import tempfile | |
| import unittest | |
| import android_misc_info | |
| import test_utils | |
| # A real misc_info.txt copied from an AOSP build for testing. | |
| _REAL_MISC_INFO_PATH = ( | |
| pathlib.Path("android_misc_info") | |
| / "misc_info.aosp_cf_arm64_only_phone-userdebug.16102939.txt" | |
| ) | |
| _REAL_MISC_INFO_PROPS = { | |
| "com.android.build.boot.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.boot.os_version": "17", | |
| "com.android.build.boot.security_patch": "2026-06-05", | |
| "com.android.build.init_boot.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.init_boot.os_version": "17", | |
| "com.android.build.init_boot.security_patch": "2026-06-05", | |
| "com.android.build.odm.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.odm.os_version": "17", | |
| "com.android.build.odm_dlkm.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.odm_dlkm.os_version": "17", | |
| "com.android.build.product.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.product.os_version": "17", | |
| "com.android.build.product.security_patch": "2026-06-05", | |
| "com.android.build.recovery.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.system.fingerprint": ( | |
| "Android/generic_system/generic:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.system.os_version": "17", | |
| "com.android.build.system.security_patch": "2026-06-05", | |
| "com.android.build.system_dlkm.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.system_dlkm.os_version": "17", | |
| "com.android.build.system_ext.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.system_ext.os_version": "17", | |
| "com.android.build.system_ext.security_patch": "2026-06-05", | |
| "com.android.build.vendor.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.vendor.os_version": "17", | |
| "com.android.build.vendor.security_patch": "2026-06-05", | |
| "com.android.build.vendor_boot.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.vendor_dlkm.fingerprint": ( | |
| "generic/aosp_cf_arm64_only_phone/vsoc_arm64_only:17/CP2A.260605.016/16102939:userdebug/test-keys" | |
| ), | |
| "com.android.build.vendor_dlkm.os_version": "17", | |
| } | |
| def run_main(input: str, flags: list[str] = []) -> str: | |
| """Runs android_misc_info.main(). | |
| Helps reduce some boilerplate by handling file I/O so that tests can just | |
| deal with strings. | |
| Args: | |
| input: the input misc_info.txt. | |
| flags: optional flags to pass to the commandline. | |
| Returns: | |
| The script output | |
| """ | |
| with tempfile.TemporaryDirectory() as temp_dir_str: | |
| temp_dir = pathlib.Path(temp_dir_str) | |
| input_path = temp_dir / "input" | |
| output_path = temp_dir / "output" | |
| input_path.write_text(input) | |
| android_misc_info.main( | |
| [str(input_path), "--output", str(output_path)] + flags | |
| ) | |
| return output_path.read_text() | |
| class AndroidMiscInfoTests(unittest.TestCase): | |
| def test_single_prop(self) -> None: | |
| output = run_main("avb_foo_args=--prop foo:bar") | |
| self.assertEqual(json.loads(output), {"foo": "bar"}) | |
| def test_multiple_props(self) -> None: | |
| output = run_main("avb_foo_args=--prop foo:bar --prop abc:123") | |
| self.assertEqual(json.loads(output), {"foo": "bar", "abc": "123"}) | |
| def test_skip_unknown_args(self) -> None: | |
| output = run_main( | |
| "avb_foo_args=--arg1 --prop foo:bar --arg2=0 --prop abc:123 --arg3" | |
| ) | |
| self.assertEqual(json.loads(output), {"foo": "bar", "abc": "123"}) | |
| def test_special_characters(self) -> None: | |
| output = run_main("avb_foo_args=--prop foo.bar.baz:ABC/123:000.000") | |
| self.assertEqual(json.loads(output), {"foo.bar.baz": "ABC/123:000.000"}) | |
| def test_extract_props_missing_value_fails(self) -> None: | |
| with self.assertRaises(ValueError) as ctx: | |
| run_main("avb_foo_args=--prop invalid_arg_no_colon") | |
| self.assertIn("Invalid property format", str(ctx.exception)) | |
| def test_extract_props_double_quoting_fails(self) -> None: | |
| with self.assertRaises(NotImplementedError) as ctx: | |
| run_main('avb_foo_args=--prop "quotes not supported"') | |
| self.assertIn("Unsupported meta-char", str(ctx.exception)) | |
| def test_extract_props_single_quoting_fails(self) -> None: | |
| with self.assertRaises(NotImplementedError) as ctx: | |
| run_main("avb_foo_args=--prop 'quotes not supported'") | |
| self.assertIn("Unsupported meta-char", str(ctx.exception)) | |
| def test_extract_props_escape_fails(self) -> None: | |
| with self.assertRaises(NotImplementedError) as ctx: | |
| run_main("avb_foo_args=--prop escape\\character") | |
| self.assertIn("Unsupported meta-char", str(ctx.exception)) | |
| def test_multiple_commandlines(self) -> None: | |
| output = run_main( | |
| "avb_foo_args=--prop foo.version:123\n" | |
| "avb_bar_args=--prop bar.version:456" | |
| ) | |
| self.assertEqual( | |
| json.loads(output), {"foo.version": "123", "bar.version": "456"} | |
| ) | |
| def test_multiple_commandlines_property_overlap_fails(self) -> None: | |
| with self.assertRaises(ValueError) as ctx: | |
| run_main( | |
| "avb_foo_args=--prop same_prop:123\n" | |
| "avb_bar_args=--prop same_prop:456" | |
| ) | |
| self.assertIn("Duplicate properties", str(ctx.exception)) | |
| def test_single_prop_filter(self) -> None: | |
| output = run_main( | |
| "avb_foo_args=--prop foo:123 --prop bar:456 --prop baz:789", | |
| flags=["--prop", "foo"], | |
| ) | |
| # Output should skip everything except `foo`. | |
| self.assertEqual(json.loads(output), {"foo": "123"}) | |
| def test_multi_prop_filter(self) -> None: | |
| output = run_main( | |
| "avb_foo_args=--prop foo:123 --prop bar:456 --prop baz:789", | |
| flags=["--prop", "foo", "--prop", "baz"], | |
| ) | |
| # Output should skip everything except `foo` and `baz`. | |
| self.assertEqual(json.loads(output), {"foo": "123", "baz": "789"}) | |
| def test_prop_rename(self) -> None: | |
| output = run_main( | |
| "avb_foo_args=--prop foo:123 --prop bar:456 --prop baz:789", | |
| flags=["--prop", "foo:new_foo", "--prop", "baz:new_baz"], | |
| ) | |
| # `foo` and `baz` should be renamed. | |
| self.assertEqual( | |
| json.loads(output), {"new_foo": "123", "new_baz": "789"} | |
| ) | |
| def test_prop_rename_same_source(self) -> None: | |
| output = run_main( | |
| "avb_foo_args=--prop foo:123", | |
| flags=["--prop", "foo:new_foo_1", "--prop", "foo:new_foo_2"], | |
| ) | |
| self.assertEqual( | |
| json.loads(output), {"new_foo_1": "123", "new_foo_2": "123"} | |
| ) | |
| def test_prop_rename_duplicate_target_fails(self) -> None: | |
| with self.assertRaises(ValueError) as ctx: | |
| run_main( | |
| "avb_foo_args=--prop foo:123 --prop bar:456", | |
| flags=["--prop", "foo:same_name", "--prop", "bar:same_name"], | |
| ) | |
| self.assertIn("Duplicate output property name", str(ctx.exception)) | |
| def test_raw_output_single_prop(self) -> None: | |
| output = run_main( | |
| "avb_foo_args=--prop foo:123 --prop bar:456 --prop baz:789", | |
| flags=["--prop", "foo", "--format", "raw_value"], | |
| ) | |
| # Output should be the raw value of "foo" without any trailing newline so that | |
| # it can be accurately read by avbtool. | |
| self.assertEqual(output, "123") | |
| def test_raw_output_multiple_props_fails(self) -> None: | |
| with self.assertRaises(ValueError) as ctx: | |
| run_main( | |
| "avb_foo_args=--prop foo:123 --prop bar:456", | |
| flags=["--format", "raw_value"], | |
| ) | |
| self.assertIn( | |
| "raw_value is only supported for a single property", | |
| str(ctx.exception), | |
| ) | |
| def test_real_misc_info(self) -> None: | |
| """Tests a real misc_info.txt from an Android build.""" | |
| contents = test_utils.load_test_data(_REAL_MISC_INFO_PATH).decode( | |
| "utf-8" | |
| ) | |
| output = run_main(contents) | |
| self.assertEqual(json.loads(output), _REAL_MISC_INFO_PROPS) | |
| if __name__ == "__main__": | |
| unittest.main() |