blob: fce09e861d4d58d4d0c154aa8facac7b17ca355f [file]
#!/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_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()