diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 37c03abf4..c384c0cfb 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -284,6 +284,225 @@ def CloseInheritedPipes(): pass +class BuildInfo(object): + """A class that holds the information for a given build. + + This class wraps up the property querying for a given source or target build. + It abstracts away the logic of handling OEM-specific properties, and caches + the commonly used properties such as fingerprint. + + There are two types of info dicts: a) build-time info dict, which is generated + at build time (i.e. included in a target_files zip); b) OEM info dict that is + specified at package generation time (via command line argument + '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not + having "oem_fingerprint_properties" in build-time info dict), all the queries + would be answered based on build-time info dict only. Otherwise if using + OEM-specific properties, some of them will be calculated from two info dicts. + + Users can query properties similarly as using a dict() (e.g. info['fstab']), + or to query build properties via GetBuildProp() or GetVendorBuildProp(). + + Attributes: + info_dict: The build-time info dict. + is_ab: Whether it's a build that uses A/B OTA. + oem_dicts: A list of OEM dicts. + oem_props: A list of OEM properties that should be read from OEM dicts; None + if the build doesn't use any OEM-specific property. + fingerprint: The fingerprint of the build, which would be calculated based + on OEM properties if applicable. + device: The device name, which could come from OEM dicts if applicable. + """ + + _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device", + "ro.product.manufacturer", "ro.product.model", + "ro.product.name"] + _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor", + "system_ext", "system"] + + def __init__(self, info_dict, oem_dicts): + """Initializes a BuildInfo instance with the given dicts. + + Note that it only wraps up the given dicts, without making copies. + + Arguments: + info_dict: The build-time info dict. + oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note + that it always uses the first dict to calculate the fingerprint or the + device name. The rest would be used for asserting OEM properties only + (e.g. one package can be installed on one of these devices). + + Raises: + ValueError: On invalid inputs. + """ + self.info_dict = info_dict + self.oem_dicts = oem_dicts + + self._is_ab = info_dict.get("ab_update") == "true" + self._oem_props = info_dict.get("oem_fingerprint_properties") + + if self._oem_props: + assert oem_dicts, "OEM source required for this build" + + # These two should be computed only after setting self._oem_props. + self._device = self.GetOemProperty("ro.product.device") + self._fingerprint = self.CalculateFingerprint() + + # Sanity check the build fingerprint. + if (' ' in self._fingerprint or + any(ord(ch) > 127 for ch in self._fingerprint)): + raise ValueError( + 'Invalid build fingerprint: "{}". See the requirement in Android CDD ' + '3.2.2. Build Parameters.'.format(self._fingerprint)) + + @property + def is_ab(self): + return self._is_ab + + @property + def device(self): + return self._device + + @property + def fingerprint(self): + return self._fingerprint + + @property + def vendor_fingerprint(self): + return self._fingerprint_of("vendor") + + @property + def product_fingerprint(self): + return self._fingerprint_of("product") + + @property + def odm_fingerprint(self): + return self._fingerprint_of("odm") + + def _fingerprint_of(self, partition): + if partition + ".build.prop" not in self.info_dict: + return None + build_prop = self.info_dict[partition + ".build.prop"] + if "ro." + partition + ".build.fingerprint" in build_prop: + return build_prop["ro." + partition + ".build.fingerprint"] + if "ro." + partition + ".build.thumbprint" in build_prop: + return build_prop["ro." + partition + ".build.thumbprint"] + return None + + @property + def oem_props(self): + return self._oem_props + + def __getitem__(self, key): + return self.info_dict[key] + + def __setitem__(self, key, value): + self.info_dict[key] = value + + def get(self, key, default=None): + return self.info_dict.get(key, default) + + def items(self): + return self.info_dict.items() + + def GetBuildProp(self, prop): + """Returns the inquired build property.""" + if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS: + return self._ResolveRoProductBuildProp(prop) + + try: + return self.info_dict.get("build.prop", {})[prop] + except KeyError: + raise ExternalError("couldn't find %s in build.prop" % (prop,)) + + def _ResolveRoProductBuildProp(self, prop): + """Resolves the inquired ro.product.* build property""" + prop_val = self.info_dict.get("build.prop", {}).get(prop) + if prop_val: + return prop_val + + source_order_val = self.info_dict.get("build.prop", {}).get( + "ro.product.property_source_order") + if source_order_val: + source_order = source_order_val.split(",") + else: + source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER + + # Check that all sources in ro.product.property_source_order are valid + if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER + for x in source_order]): + raise ExternalError( + "Invalid ro.product.property_source_order '{}'".format(source_order)) + + for source in source_order: + source_prop = prop.replace( + "ro.product", "ro.product.{}".format(source), 1) + prop_val = self.info_dict.get( + "{}.build.prop".format(source), {}).get(source_prop) + if prop_val: + return prop_val + + raise ExternalError("couldn't resolve {}".format(prop)) + + def GetVendorBuildProp(self, prop): + """Returns the inquired vendor build property.""" + try: + return self.info_dict.get("vendor.build.prop", {})[prop] + except KeyError: + raise ExternalError( + "couldn't find %s in vendor.build.prop" % (prop,)) + + def GetOemProperty(self, key): + if self.oem_props is not None and key in self.oem_props: + return self.oem_dicts[0][key] + return self.GetBuildProp(key) + + def CalculateFingerprint(self): + if self.oem_props is None: + try: + return self.GetBuildProp("ro.build.fingerprint") + except ExternalError: + return "{}/{}/{}:{}/{}/{}:{}/{}".format( + self.GetBuildProp("ro.product.brand"), + self.GetBuildProp("ro.product.name"), + self.GetBuildProp("ro.product.device"), + self.GetBuildProp("ro.build.version.release"), + self.GetBuildProp("ro.build.id"), + self.GetBuildProp("ro.build.version.incremental"), + self.GetBuildProp("ro.build.type"), + self.GetBuildProp("ro.build.tags")) + return "%s/%s/%s:%s" % ( + self.GetOemProperty("ro.product.brand"), + self.GetOemProperty("ro.product.name"), + self.GetOemProperty("ro.product.device"), + self.GetBuildProp("ro.build.thumbprint")) + + def WriteMountOemScript(self, script): + assert self.oem_props is not None + recovery_mount_options = self.info_dict.get("recovery_mount_options") + script.Mount("/oem", recovery_mount_options) + + def WriteDeviceAssertions(self, script, oem_no_mount): + # Read the property directly if not using OEM properties. + if not self.oem_props: + script.AssertDevice(self.device) + return + + # Otherwise assert OEM properties. + if not self.oem_dicts: + raise ExternalError( + "No OEM file provided to answer expected assertions") + + for prop in self.oem_props.split(): + values = [] + for oem_dict in self.oem_dicts: + if prop in oem_dict: + values.append(oem_dict[prop]) + if not values: + raise ExternalError( + "The OEM file is missing the property %s" % (prop,)) + script.AssertOemProperty(prop, values, oem_no_mount) + + def LoadInfoDict(input_file, repacking=False): """Loads the key/value pairs from the given input target_files. diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index 3fc3c2249..1e7bb3a94 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -258,225 +258,6 @@ SECONDARY_PAYLOAD_SKIPPED_IMAGES = [ 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor'] -class BuildInfo(object): - """A class that holds the information for a given build. - - This class wraps up the property querying for a given source or target build. - It abstracts away the logic of handling OEM-specific properties, and caches - the commonly used properties such as fingerprint. - - There are two types of info dicts: a) build-time info dict, which is generated - at build time (i.e. included in a target_files zip); b) OEM info dict that is - specified at package generation time (via command line argument - '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not - having "oem_fingerprint_properties" in build-time info dict), all the queries - would be answered based on build-time info dict only. Otherwise if using - OEM-specific properties, some of them will be calculated from two info dicts. - - Users can query properties similarly as using a dict() (e.g. info['fstab']), - or to query build properties via GetBuildProp() or GetVendorBuildProp(). - - Attributes: - info_dict: The build-time info dict. - is_ab: Whether it's a build that uses A/B OTA. - oem_dicts: A list of OEM dicts. - oem_props: A list of OEM properties that should be read from OEM dicts; None - if the build doesn't use any OEM-specific property. - fingerprint: The fingerprint of the build, which would be calculated based - on OEM properties if applicable. - device: The device name, which could come from OEM dicts if applicable. - """ - - _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device", - "ro.product.manufacturer", "ro.product.model", - "ro.product.name"] - _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor", - "system_ext", "system"] - - def __init__(self, info_dict, oem_dicts): - """Initializes a BuildInfo instance with the given dicts. - - Note that it only wraps up the given dicts, without making copies. - - Arguments: - info_dict: The build-time info dict. - oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note - that it always uses the first dict to calculate the fingerprint or the - device name. The rest would be used for asserting OEM properties only - (e.g. one package can be installed on one of these devices). - - Raises: - ValueError: On invalid inputs. - """ - self.info_dict = info_dict - self.oem_dicts = oem_dicts - - self._is_ab = info_dict.get("ab_update") == "true" - self._oem_props = info_dict.get("oem_fingerprint_properties") - - if self._oem_props: - assert oem_dicts, "OEM source required for this build" - - # These two should be computed only after setting self._oem_props. - self._device = self.GetOemProperty("ro.product.device") - self._fingerprint = self.CalculateFingerprint() - - # Sanity check the build fingerprint. - if (' ' in self._fingerprint or - any(ord(ch) > 127 for ch in self._fingerprint)): - raise ValueError( - 'Invalid build fingerprint: "{}". See the requirement in Android CDD ' - '3.2.2. Build Parameters.'.format(self._fingerprint)) - - @property - def is_ab(self): - return self._is_ab - - @property - def device(self): - return self._device - - @property - def fingerprint(self): - return self._fingerprint - - @property - def vendor_fingerprint(self): - return self._fingerprint_of("vendor") - - @property - def product_fingerprint(self): - return self._fingerprint_of("product") - - @property - def odm_fingerprint(self): - return self._fingerprint_of("odm") - - def _fingerprint_of(self, partition): - if partition + ".build.prop" not in self.info_dict: - return None - build_prop = self.info_dict[partition + ".build.prop"] - if "ro." + partition + ".build.fingerprint" in build_prop: - return build_prop["ro." + partition + ".build.fingerprint"] - if "ro." + partition + ".build.thumbprint" in build_prop: - return build_prop["ro." + partition + ".build.thumbprint"] - return None - - @property - def oem_props(self): - return self._oem_props - - def __getitem__(self, key): - return self.info_dict[key] - - def __setitem__(self, key, value): - self.info_dict[key] = value - - def get(self, key, default=None): - return self.info_dict.get(key, default) - - def items(self): - return self.info_dict.items() - - def GetBuildProp(self, prop): - """Returns the inquired build property.""" - if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS: - return self._ResolveRoProductBuildProp(prop) - - try: - return self.info_dict.get("build.prop", {})[prop] - except KeyError: - raise common.ExternalError("couldn't find %s in build.prop" % (prop,)) - - def _ResolveRoProductBuildProp(self, prop): - """Resolves the inquired ro.product.* build property""" - prop_val = self.info_dict.get("build.prop", {}).get(prop) - if prop_val: - return prop_val - - source_order_val = self.info_dict.get("build.prop", {}).get( - "ro.product.property_source_order") - if source_order_val: - source_order = source_order_val.split(",") - else: - source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER - - # Check that all sources in ro.product.property_source_order are valid - if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER - for x in source_order]): - raise common.ExternalError( - "Invalid ro.product.property_source_order '{}'".format(source_order)) - - for source in source_order: - source_prop = prop.replace( - "ro.product", "ro.product.{}".format(source), 1) - prop_val = self.info_dict.get( - "{}.build.prop".format(source), {}).get(source_prop) - if prop_val: - return prop_val - - raise common.ExternalError("couldn't resolve {}".format(prop)) - - def GetVendorBuildProp(self, prop): - """Returns the inquired vendor build property.""" - try: - return self.info_dict.get("vendor.build.prop", {})[prop] - except KeyError: - raise common.ExternalError( - "couldn't find %s in vendor.build.prop" % (prop,)) - - def GetOemProperty(self, key): - if self.oem_props is not None and key in self.oem_props: - return self.oem_dicts[0][key] - return self.GetBuildProp(key) - - def CalculateFingerprint(self): - if self.oem_props is None: - try: - return self.GetBuildProp("ro.build.fingerprint") - except common.ExternalError: - return "{}/{}/{}:{}/{}/{}:{}/{}".format( - self.GetBuildProp("ro.product.brand"), - self.GetBuildProp("ro.product.name"), - self.GetBuildProp("ro.product.device"), - self.GetBuildProp("ro.build.version.release"), - self.GetBuildProp("ro.build.id"), - self.GetBuildProp("ro.build.version.incremental"), - self.GetBuildProp("ro.build.type"), - self.GetBuildProp("ro.build.tags")) - return "%s/%s/%s:%s" % ( - self.GetOemProperty("ro.product.brand"), - self.GetOemProperty("ro.product.name"), - self.GetOemProperty("ro.product.device"), - self.GetBuildProp("ro.build.thumbprint")) - - def WriteMountOemScript(self, script): - assert self.oem_props is not None - recovery_mount_options = self.info_dict.get("recovery_mount_options") - script.Mount("/oem", recovery_mount_options) - - def WriteDeviceAssertions(self, script, oem_no_mount): - # Read the property directly if not using OEM properties. - if not self.oem_props: - script.AssertDevice(self.device) - return - - # Otherwise assert OEM properties. - if not self.oem_dicts: - raise common.ExternalError( - "No OEM file provided to answer expected assertions") - - for prop in self.oem_props.split(): - values = [] - for oem_dict in self.oem_dicts: - if prop in oem_dict: - values.append(oem_dict[prop]) - if not values: - raise common.ExternalError( - "The OEM file is missing the property %s" % (prop,)) - script.AssertOemProperty(prop, values, oem_no_mount) - - class PayloadSigner(object): """A class that wraps the payload signing works. @@ -904,7 +685,7 @@ def GetBlockDifferences(target_zip, source_zip, target_info, source_info, def WriteFullOTAPackage(input_zip, output_file): - target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) + target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) # We don't know what version it will be installed on top of. We expect the API # just won't change very often. Similarly for fstab, it might have changed in @@ -1130,8 +911,8 @@ def GetPackageMetadata(target_info, source_info=None): Returns: A dict to be written into package metadata entry. """ - assert isinstance(target_info, BuildInfo) - assert source_info is None or isinstance(source_info, BuildInfo) + assert isinstance(target_info, common.BuildInfo) + assert source_info is None or isinstance(source_info, common.BuildInfo) metadata = { 'post-build' : target_info.fingerprint, @@ -1544,8 +1325,8 @@ def FinalizeMetadata(metadata, input_file, output_file, needed_property_files): def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file): - target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts) - source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts) + target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts) + source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts) target_api_version = target_info["recovery_api_version"] source_api_version = source_info["recovery_api_version"] @@ -2024,10 +1805,10 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None): compression=zipfile.ZIP_DEFLATED) if source_file is not None: - target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts) - source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts) + target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts) + source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts) else: - target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) + target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) source_info = None # Metadata to comply with Android OTA package format. diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py index c1e9d7a1f..59b05e9d9 100644 --- a/tools/releasetools/test_common.py +++ b/tools/releasetools/test_common.py @@ -44,6 +44,210 @@ def get_2gb_string(): yield b'\0' * (step_size - block_size) +class BuildInfoTest(test_utils.ReleaseToolsTestCase): + + TEST_INFO_DICT = { + 'build.prop' : { + 'ro.product.device' : 'product-device', + 'ro.product.name' : 'product-name', + 'ro.build.fingerprint' : 'build-fingerprint', + 'ro.build.foo' : 'build-foo', + }, + 'vendor.build.prop' : { + 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint', + }, + 'property1' : 'value1', + 'property2' : 4096, + } + + TEST_INFO_DICT_USES_OEM_PROPS = { + 'build.prop' : { + 'ro.product.name' : 'product-name', + 'ro.build.thumbprint' : 'build-thumbprint', + 'ro.build.bar' : 'build-bar', + }, + 'vendor.build.prop' : { + 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint', + }, + 'property1' : 'value1', + 'property2' : 4096, + 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand', + } + + TEST_OEM_DICTS = [ + { + 'ro.product.brand' : 'brand1', + 'ro.product.device' : 'device1', + }, + { + 'ro.product.brand' : 'brand2', + 'ro.product.device' : 'device2', + }, + { + 'ro.product.brand' : 'brand3', + 'ro.product.device' : 'device3', + }, + ] + + def test_init(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + self.assertEqual('product-device', target_info.device) + self.assertEqual('build-fingerprint', target_info.fingerprint) + self.assertFalse(target_info.is_ab) + self.assertIsNone(target_info.oem_props) + + def test_init_with_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + self.assertEqual('device1', target_info.device) + self.assertEqual('brand1/product-name/device1:build-thumbprint', + target_info.fingerprint) + + # Swap the order in oem_dicts, which would lead to different BuildInfo. + oem_dicts = copy.copy(self.TEST_OEM_DICTS) + oem_dicts[0], oem_dicts[2] = oem_dicts[2], oem_dicts[0] + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + oem_dicts) + self.assertEqual('device3', target_info.device) + self.assertEqual('brand3/product-name/device3:build-thumbprint', + target_info.fingerprint) + + # Missing oem_dict should be rejected. + self.assertRaises(AssertionError, common.BuildInfo, + self.TEST_INFO_DICT_USES_OEM_PROPS, None) + + def test_init_badFingerprint(self): + info_dict = copy.deepcopy(self.TEST_INFO_DICT) + info_dict['build.prop']['ro.build.fingerprint'] = 'bad fingerprint' + self.assertRaises(ValueError, common.BuildInfo, info_dict, None) + + info_dict['build.prop']['ro.build.fingerprint'] = 'bad\x80fingerprint' + self.assertRaises(ValueError, common.BuildInfo, info_dict, None) + + def test___getitem__(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + self.assertEqual('value1', target_info['property1']) + self.assertEqual(4096, target_info['property2']) + self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo']) + + def test___getitem__with_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + self.assertEqual('value1', target_info['property1']) + self.assertEqual(4096, target_info['property2']) + self.assertRaises(KeyError, + lambda: target_info['build.prop']['ro.build.foo']) + + def test___setitem__(self): + target_info = common.BuildInfo(copy.deepcopy(self.TEST_INFO_DICT), None) + self.assertEqual('value1', target_info['property1']) + target_info['property1'] = 'value2' + self.assertEqual('value2', target_info['property1']) + + self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo']) + target_info['build.prop']['ro.build.foo'] = 'build-bar' + self.assertEqual('build-bar', target_info['build.prop']['ro.build.foo']) + + def test_get(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + self.assertEqual('value1', target_info.get('property1')) + self.assertEqual(4096, target_info.get('property2')) + self.assertEqual(4096, target_info.get('property2', 1024)) + self.assertEqual(1024, target_info.get('property-nonexistent', 1024)) + self.assertEqual('build-foo', target_info.get('build.prop')['ro.build.foo']) + + def test_get_with_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + self.assertEqual('value1', target_info.get('property1')) + self.assertEqual(4096, target_info.get('property2')) + self.assertEqual(4096, target_info.get('property2', 1024)) + self.assertEqual(1024, target_info.get('property-nonexistent', 1024)) + self.assertIsNone(target_info.get('build.prop').get('ro.build.foo')) + self.assertRaises(KeyError, + lambda: target_info.get('build.prop')['ro.build.foo']) + + def test_items(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + items = target_info.items() + self.assertIn(('property1', 'value1'), items) + self.assertIn(('property2', 4096), items) + + def test_GetBuildProp(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + self.assertEqual('build-foo', target_info.GetBuildProp('ro.build.foo')) + self.assertRaises(common.ExternalError, target_info.GetBuildProp, + 'ro.build.nonexistent') + + def test_GetBuildProp_with_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + self.assertEqual('build-bar', target_info.GetBuildProp('ro.build.bar')) + self.assertRaises(common.ExternalError, target_info.GetBuildProp, + 'ro.build.nonexistent') + + def test_GetVendorBuildProp(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + self.assertEqual('vendor-build-fingerprint', + target_info.GetVendorBuildProp( + 'ro.vendor.build.fingerprint')) + self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp, + 'ro.build.nonexistent') + + def test_GetVendorBuildProp_with_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + self.assertEqual('vendor-build-fingerprint', + target_info.GetVendorBuildProp( + 'ro.vendor.build.fingerprint')) + self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp, + 'ro.build.nonexistent') + + def test_vendor_fingerprint(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + self.assertEqual('vendor-build-fingerprint', + target_info.vendor_fingerprint) + + def test_vendor_fingerprint_blacklisted(self): + target_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS) + del target_info_dict['vendor.build.prop']['ro.vendor.build.fingerprint'] + target_info = common.BuildInfo(target_info_dict, self.TEST_OEM_DICTS) + self.assertIsNone(target_info.vendor_fingerprint) + + def test_vendor_fingerprint_without_vendor_build_prop(self): + target_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS) + del target_info_dict['vendor.build.prop'] + target_info = common.BuildInfo(target_info_dict, self.TEST_OEM_DICTS) + self.assertIsNone(target_info.vendor_fingerprint) + + def test_WriteMountOemScript(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + script_writer = test_utils.MockScriptWriter() + target_info.WriteMountOemScript(script_writer) + self.assertEqual([('Mount', '/oem', None)], script_writer.lines) + + def test_WriteDeviceAssertions(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT, None) + script_writer = test_utils.MockScriptWriter() + target_info.WriteDeviceAssertions(script_writer, False) + self.assertEqual([('AssertDevice', 'product-device')], script_writer.lines) + + def test_WriteDeviceAssertions_with_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + script_writer = test_utils.MockScriptWriter() + target_info.WriteDeviceAssertions(script_writer, False) + self.assertEqual( + [ + ('AssertOemProperty', 'ro.product.device', + ['device1', 'device2', 'device3'], False), + ('AssertOemProperty', 'ro.product.brand', + ['brand1', 'brand2', 'brand3'], False), + ], + script_writer.lines) + + class CommonZipTest(test_utils.ReleaseToolsTestCase): def _verify(self, zip_file, zip_file_name, arcname, expected_hash, diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py index dd42822bb..c3021a16f 100644 --- a/tools/releasetools/test_ota_from_target_files.py +++ b/tools/releasetools/test_ota_from_target_files.py @@ -22,7 +22,7 @@ import zipfile import common import test_utils from ota_from_target_files import ( - _LoadOemDicts, AbOtaPropertyFiles, BuildInfo, FinalizeMetadata, + _LoadOemDicts, AbOtaPropertyFiles, FinalizeMetadata, GetPackageMetadata, GetTargetFilesZipForSecondaryImages, GetTargetFilesZipWithoutPostinstallConfig, NonAbOtaPropertyFiles, Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles, @@ -74,262 +74,6 @@ def construct_target_files(secondary=False): return target_files -class BuildInfoTest(test_utils.ReleaseToolsTestCase): - - TEST_INFO_DICT = { - 'build.prop' : { - 'ro.product.device' : 'product-device', - 'ro.product.name' : 'product-name', - 'ro.build.fingerprint' : 'build-fingerprint', - 'ro.build.foo' : 'build-foo', - }, - 'vendor.build.prop' : { - 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint', - }, - 'property1' : 'value1', - 'property2' : 4096, - } - - TEST_INFO_DICT_USES_OEM_PROPS = { - 'build.prop' : { - 'ro.product.name' : 'product-name', - 'ro.build.thumbprint' : 'build-thumbprint', - 'ro.build.bar' : 'build-bar', - }, - 'vendor.build.prop' : { - 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint', - }, - 'property1' : 'value1', - 'property2' : 4096, - 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand', - } - - TEST_OEM_DICTS = [ - { - 'ro.product.brand' : 'brand1', - 'ro.product.device' : 'device1', - }, - { - 'ro.product.brand' : 'brand2', - 'ro.product.device' : 'device2', - }, - { - 'ro.product.brand' : 'brand3', - 'ro.product.device' : 'device3', - }, - ] - - def test_init(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - self.assertEqual('product-device', target_info.device) - self.assertEqual('build-fingerprint', target_info.fingerprint) - self.assertFalse(target_info.is_ab) - self.assertIsNone(target_info.oem_props) - - def test_init_with_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - self.assertEqual('device1', target_info.device) - self.assertEqual('brand1/product-name/device1:build-thumbprint', - target_info.fingerprint) - - # Swap the order in oem_dicts, which would lead to different BuildInfo. - oem_dicts = copy.copy(self.TEST_OEM_DICTS) - oem_dicts[0], oem_dicts[2] = oem_dicts[2], oem_dicts[0] - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, oem_dicts) - self.assertEqual('device3', target_info.device) - self.assertEqual('brand3/product-name/device3:build-thumbprint', - target_info.fingerprint) - - # Missing oem_dict should be rejected. - self.assertRaises(AssertionError, BuildInfo, - self.TEST_INFO_DICT_USES_OEM_PROPS, None) - - def test_init_badFingerprint(self): - info_dict = copy.deepcopy(self.TEST_INFO_DICT) - info_dict['build.prop']['ro.build.fingerprint'] = 'bad fingerprint' - self.assertRaises(ValueError, BuildInfo, info_dict, None) - - info_dict['build.prop']['ro.build.fingerprint'] = 'bad\x80fingerprint' - self.assertRaises(ValueError, BuildInfo, info_dict, None) - - def test___getitem__(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - self.assertEqual('value1', target_info['property1']) - self.assertEqual(4096, target_info['property2']) - self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo']) - - def test___getitem__with_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - self.assertEqual('value1', target_info['property1']) - self.assertEqual(4096, target_info['property2']) - self.assertRaises(KeyError, - lambda: target_info['build.prop']['ro.build.foo']) - - def test___setitem__(self): - target_info = BuildInfo(copy.deepcopy(self.TEST_INFO_DICT), None) - self.assertEqual('value1', target_info['property1']) - target_info['property1'] = 'value2' - self.assertEqual('value2', target_info['property1']) - - self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo']) - target_info['build.prop']['ro.build.foo'] = 'build-bar' - self.assertEqual('build-bar', target_info['build.prop']['ro.build.foo']) - - def test_get(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - self.assertEqual('value1', target_info.get('property1')) - self.assertEqual(4096, target_info.get('property2')) - self.assertEqual(4096, target_info.get('property2', 1024)) - self.assertEqual(1024, target_info.get('property-nonexistent', 1024)) - self.assertEqual('build-foo', target_info.get('build.prop')['ro.build.foo']) - - def test_get_with_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - self.assertEqual('value1', target_info.get('property1')) - self.assertEqual(4096, target_info.get('property2')) - self.assertEqual(4096, target_info.get('property2', 1024)) - self.assertEqual(1024, target_info.get('property-nonexistent', 1024)) - self.assertIsNone(target_info.get('build.prop').get('ro.build.foo')) - self.assertRaises(KeyError, - lambda: target_info.get('build.prop')['ro.build.foo']) - - def test_items(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - items = target_info.items() - self.assertIn(('property1', 'value1'), items) - self.assertIn(('property2', 4096), items) - - def test_GetBuildProp(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - self.assertEqual('build-foo', target_info.GetBuildProp('ro.build.foo')) - self.assertRaises(common.ExternalError, target_info.GetBuildProp, - 'ro.build.nonexistent') - - def test_GetBuildProp_with_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - self.assertEqual('build-bar', target_info.GetBuildProp('ro.build.bar')) - self.assertRaises(common.ExternalError, target_info.GetBuildProp, - 'ro.build.nonexistent') - - def test_GetVendorBuildProp(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - self.assertEqual('vendor-build-fingerprint', - target_info.GetVendorBuildProp( - 'ro.vendor.build.fingerprint')) - self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp, - 'ro.build.nonexistent') - - def test_GetVendorBuildProp_with_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - self.assertEqual('vendor-build-fingerprint', - target_info.GetVendorBuildProp( - 'ro.vendor.build.fingerprint')) - self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp, - 'ro.build.nonexistent') - - def test_vendor_fingerprint(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - self.assertEqual('vendor-build-fingerprint', - target_info.vendor_fingerprint) - - def test_vendor_fingerprint_blacklisted(self): - target_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS) - del target_info_dict['vendor.build.prop']['ro.vendor.build.fingerprint'] - target_info = BuildInfo(target_info_dict, self.TEST_OEM_DICTS) - self.assertIsNone(target_info.vendor_fingerprint) - - def test_vendor_fingerprint_without_vendor_build_prop(self): - target_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS) - del target_info_dict['vendor.build.prop'] - target_info = BuildInfo(target_info_dict, self.TEST_OEM_DICTS) - self.assertIsNone(target_info.vendor_fingerprint) - - def test_WriteMountOemScript(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - script_writer = test_utils.MockScriptWriter() - target_info.WriteMountOemScript(script_writer) - self.assertEqual([('Mount', '/oem', None)], script_writer.lines) - - def test_WriteDeviceAssertions(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - script_writer = test_utils.MockScriptWriter() - target_info.WriteDeviceAssertions(script_writer, False) - self.assertEqual([('AssertDevice', 'product-device')], script_writer.lines) - - def test_WriteDeviceAssertions_with_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - script_writer = test_utils.MockScriptWriter() - target_info.WriteDeviceAssertions(script_writer, False) - self.assertEqual( - [ - ('AssertOemProperty', 'ro.product.device', - ['device1', 'device2', 'device3'], False), - ('AssertOemProperty', 'ro.product.brand', - ['brand1', 'brand2', 'brand3'], False), - ], - script_writer.lines) - - def test_WriteFingerprintAssertion_without_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - source_info_dict = copy.deepcopy(self.TEST_INFO_DICT) - source_info_dict['build.prop']['ro.build.fingerprint'] = ( - 'source-build-fingerprint') - source_info = BuildInfo(source_info_dict, None) - - script_writer = test_utils.MockScriptWriter() - WriteFingerprintAssertion(script_writer, target_info, source_info) - self.assertEqual( - [('AssertSomeFingerprint', 'source-build-fingerprint', - 'build-fingerprint')], - script_writer.lines) - - def test_WriteFingerprintAssertion_with_source_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT, None) - source_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - - script_writer = test_utils.MockScriptWriter() - WriteFingerprintAssertion(script_writer, target_info, source_info) - self.assertEqual( - [('AssertFingerprintOrThumbprint', 'build-fingerprint', - 'build-thumbprint')], - script_writer.lines) - - def test_WriteFingerprintAssertion_with_target_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - source_info = BuildInfo(self.TEST_INFO_DICT, None) - - script_writer = test_utils.MockScriptWriter() - WriteFingerprintAssertion(script_writer, target_info, source_info) - self.assertEqual( - [('AssertFingerprintOrThumbprint', 'build-fingerprint', - 'build-thumbprint')], - script_writer.lines) - - def test_WriteFingerprintAssertion_with_both_oem_props(self): - target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, - self.TEST_OEM_DICTS) - source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS) - source_info_dict['build.prop']['ro.build.thumbprint'] = ( - 'source-build-thumbprint') - source_info = BuildInfo(source_info_dict, self.TEST_OEM_DICTS) - - script_writer = test_utils.MockScriptWriter() - WriteFingerprintAssertion(script_writer, target_info, source_info) - self.assertEqual( - [('AssertSomeThumbprint', 'build-thumbprint', - 'source-build-thumbprint')], - script_writer.lines) - - class LoadOemDictsTest(test_utils.ReleaseToolsTestCase): def test_NoneDict(self): @@ -387,6 +131,35 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): }, } + TEST_INFO_DICT_USES_OEM_PROPS = { + 'build.prop' : { + 'ro.product.name' : 'product-name', + 'ro.build.thumbprint' : 'build-thumbprint', + 'ro.build.bar' : 'build-bar', + }, + 'vendor.build.prop' : { + 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint', + }, + 'property1' : 'value1', + 'property2' : 4096, + 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand', + } + + TEST_OEM_DICTS = [ + { + 'ro.product.brand' : 'brand1', + 'ro.product.device' : 'device1', + }, + { + 'ro.product.brand' : 'brand2', + 'ro.product.device' : 'device2', + }, + { + 'ro.product.brand' : 'brand3', + 'ro.product.device' : 'device3', + }, + ] + def setUp(self): self.testdata_dir = test_utils.get_testdata_dir() self.assertTrue(os.path.exists(self.testdata_dir)) @@ -408,7 +181,7 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): def test_GetPackageMetadata_abOta_full(self): target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) target_info_dict['ab_update'] = 'true' - target_info = BuildInfo(target_info_dict, None) + target_info = common.BuildInfo(target_info_dict, None) metadata = GetPackageMetadata(target_info) self.assertDictEqual( { @@ -426,8 +199,8 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): def test_GetPackageMetadata_abOta_incremental(self): target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) target_info_dict['ab_update'] = 'true' - target_info = BuildInfo(target_info_dict, None) - source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None) + target_info = common.BuildInfo(target_info_dict, None) + source_info = common.BuildInfo(self.TEST_SOURCE_INFO_DICT, None) common.OPTIONS.incremental_source = '' metadata = GetPackageMetadata(target_info, source_info) self.assertDictEqual( @@ -446,7 +219,7 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): metadata) def test_GetPackageMetadata_nonAbOta_full(self): - target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None) + target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) metadata = GetPackageMetadata(target_info) self.assertDictEqual( { @@ -461,8 +234,8 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): metadata) def test_GetPackageMetadata_nonAbOta_incremental(self): - target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None) - source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None) + target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) + source_info = common.BuildInfo(self.TEST_SOURCE_INFO_DICT, None) common.OPTIONS.incremental_source = '' metadata = GetPackageMetadata(target_info, source_info) self.assertDictEqual( @@ -480,7 +253,7 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): metadata) def test_GetPackageMetadata_wipe(self): - target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None) + target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) common.OPTIONS.wipe_user_data = True metadata = GetPackageMetadata(target_info) self.assertDictEqual( @@ -497,7 +270,7 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): metadata) def test_GetPackageMetadata_retrofitDynamicPartitions(self): - target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None) + target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) common.OPTIONS.retrofit_dynamic_partitions = True metadata = GetPackageMetadata(target_info) self.assertDictEqual( @@ -526,8 +299,8 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): self._test_GetPackageMetadata_swapBuildTimestamps( target_info_dict, source_info_dict) - target_info = BuildInfo(target_info_dict, None) - source_info = BuildInfo(source_info_dict, None) + target_info = common.BuildInfo(target_info_dict, None) + source_info = common.BuildInfo(source_info_dict, None) common.OPTIONS.incremental_source = '' self.assertRaises(RuntimeError, GetPackageMetadata, target_info, source_info) @@ -538,8 +311,8 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): self._test_GetPackageMetadata_swapBuildTimestamps( target_info_dict, source_info_dict) - target_info = BuildInfo(target_info_dict, None) - source_info = BuildInfo(source_info_dict, None) + target_info = common.BuildInfo(target_info_dict, None) + source_info = common.BuildInfo(source_info_dict, None) common.OPTIONS.incremental_source = '' common.OPTIONS.downgrade = True common.OPTIONS.wipe_user_data = True @@ -752,6 +525,59 @@ class OtaFromTargetFilesTest(test_utils.ReleaseToolsTestCase): FinalizeMetadata(metadata, zip_file, output_file, needed_property_files) self.assertIn('ota-test-property-files', metadata) + def test_WriteFingerprintAssertion_without_oem_props(self): + target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) + source_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT) + source_info_dict['build.prop']['ro.build.fingerprint'] = ( + 'source-build-fingerprint') + source_info = common.BuildInfo(source_info_dict, None) + + script_writer = test_utils.MockScriptWriter() + WriteFingerprintAssertion(script_writer, target_info, source_info) + self.assertEqual( + [('AssertSomeFingerprint', 'source-build-fingerprint', + 'build-fingerprint-target')], + script_writer.lines) + + def test_WriteFingerprintAssertion_with_source_oem_props(self): + target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) + source_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + + script_writer = test_utils.MockScriptWriter() + WriteFingerprintAssertion(script_writer, target_info, source_info) + self.assertEqual( + [('AssertFingerprintOrThumbprint', 'build-fingerprint-target', + 'build-thumbprint')], + script_writer.lines) + + def test_WriteFingerprintAssertion_with_target_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + source_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None) + + script_writer = test_utils.MockScriptWriter() + WriteFingerprintAssertion(script_writer, target_info, source_info) + self.assertEqual( + [('AssertFingerprintOrThumbprint', 'build-fingerprint-target', + 'build-thumbprint')], + script_writer.lines) + + def test_WriteFingerprintAssertion_with_both_oem_props(self): + target_info = common.BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, + self.TEST_OEM_DICTS) + source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS) + source_info_dict['build.prop']['ro.build.thumbprint'] = ( + 'source-build-thumbprint') + source_info = common.BuildInfo(source_info_dict, self.TEST_OEM_DICTS) + + script_writer = test_utils.MockScriptWriter() + WriteFingerprintAssertion(script_writer, target_info, source_info) + self.assertEqual( + [('AssertSomeThumbprint', 'build-thumbprint', + 'source-build-thumbprint')], + script_writer.lines) + class TestPropertyFiles(PropertyFiles): """A class that extends PropertyFiles for testing purpose."""