Merge "releasetools: Add unittests for GetCareMap() and AddCareMapTxtForAbOta()."
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index c3694ab2..baa2344 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -196,6 +196,7 @@
LOCAL_PREBUILT_OBJ_FILES:=
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES:=
LOCAL_PREBUILT_STRIP_COMMENTS:=
+LOCAL_PRIVATE_PLATFORM_APIS:=
LOCAL_PRIVILEGED_MODULE:=
# '',full,custom,disabled,obfuscation,optimization
LOCAL_PRODUCT_MODULE:=
@@ -239,6 +240,8 @@
LOCAL_SOONG_PROGUARD_DICT :=
LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=
LOCAL_SOONG_RRO_DIRS :=
+LOCAL_DROIDDOC_STUBS_JAR :=
+LOCAL_DROIDDOC_DOC_ZIP :=
# '',true
LOCAL_SOURCE_FILES_ALL_GENERATED:=
LOCAL_SRC_FILES:=
diff --git a/core/host_dalvik_java_library.mk b/core/host_dalvik_java_library.mk
index 43fc780..20663d1 100644
--- a/core/host_dalvik_java_library.mk
+++ b/core/host_dalvik_java_library.mk
@@ -67,6 +67,8 @@
include $(BUILD_SYSTEM)/java_common.mk
+include $(BUILD_SYSTEM)/sdk_check.mk
+
$(cleantarget): PRIVATE_CLEAN_FILES += $(intermediates.COMMON)
# List of dependencies for anything that needs all java sources in place
diff --git a/core/java.mk b/core/java.mk
index f92cbca..442d6cb 100644
--- a/core/java.mk
+++ b/core/java.mk
@@ -357,6 +357,8 @@
include $(BUILD_SYSTEM)/java_common.mk
+include $(BUILD_SYSTEM)/sdk_check.mk
+
$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_HAS_RS_SOURCES := $(if $(renderscript_sources),true)
$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_RS_SOURCE_INTERMEDIATES_DIR := $(intermediates.COMMON)/renderscript
diff --git a/core/sdk_check.mk b/core/sdk_check.mk
new file mode 100644
index 0000000..c5c2bc8
--- /dev/null
+++ b/core/sdk_check.mk
@@ -0,0 +1,14 @@
+
+# Enforcement checks that LOCAL_SDK_VERSION and LOCAL_PRIVATE_PLATFORM_APIS are
+# set correctly.
+# Should be included by java targets that allow specifying LOCAL_SDK_VERSION.
+
+ifeq ($(LOCAL_SDK_VERSION)$(LOCAL_PRIVATE_PLATFORM_APIS),)
+ifneq ($(JAVA_SDK_ENFORCEMENT_WARNING),)
+$(warning Java modules must specify LOCAL_SDK_VERSION or LOCAL_PRIVATE_PLATFORM_APIS, but $(LOCAL_MODULE) specifies neither.)
+endif
+else ifneq ($(LOCAL_SDK_VERSION),)
+ifneq ($(LOCAL_PRIVATE_PLATFORM_APIS),)
+$(error $(LOCAL_MODULE) specifies both LOCAL_SDK_VERSION ($(LOCAL_SDK_VERSION)) and LOCAL_PRIVATE_PLATFORM_APIS ($(LOCAL_PRIVATE_PLATFORM_APIS)), but should specify only one.)
+endif
+endif
diff --git a/core/soong_config.mk b/core/soong_config.mk
index a084f79..8b2dfd1 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -42,6 +42,9 @@
$(call add_json_str, Make_suffix, -$(TARGET_PRODUCT))
+$(call add_json_str, BuildId, $(BUILD_ID))
+$(call add_json_str, BuildNumberFromFile, $$$(BUILD_NUMBER_FROM_FILE))
+
$(call add_json_val, Platform_sdk_version, $(PLATFORM_SDK_VERSION))
$(call add_json_csv, Platform_version_active_codenames, $(PLATFORM_VERSION_ALL_CODENAMES))
$(call add_json_csv, Platform_version_future_codenames, $(PLATFORM_VERSION_FUTURE_CODENAMES))
diff --git a/core/soong_java_prebuilt.mk b/core/soong_java_prebuilt.mk
index f3ed376..f10da32 100644
--- a/core/soong_java_prebuilt.mk
+++ b/core/soong_java_prebuilt.mk
@@ -23,6 +23,15 @@
$(eval $(call copy-one-file,$(LOCAL_PREBUILT_MODULE_FILE),$(full_classes_jar)))
$(eval $(call copy-one-file,$(LOCAL_PREBUILT_MODULE_FILE),$(full_classes_pre_proguard_jar)))
+ifdef LOCAL_DROIDDOC_STUBS_JAR
+$(eval $(call copy-one-file,$(LOCAL_DROIDDOC_STUBS_JAR),$(OUT_DOCS)/$(LOCAL_MODULE)-stubs.jar))
+ALL_DOCS += $(OUT_DOCS)/$(LOCAL_MODULE)-stubs.jar
+endif
+
+ifdef LOCAL_DROIDDOC_DOC_ZIP
+$(eval $(call copy-one-file,$(LOCAL_DROIDDOC_DOC_ZIP),$(OUT_DOCS)/$(LOCAL_MODULE)-docs.zip))
+endif
+
ifdef LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR
$(eval $(call copy-one-file,$(LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR),\
$(intermediates.COMMON)/jacoco-report-classes.jar))
diff --git a/target/board/generic/sepolicy/property.te b/target/board/generic/sepolicy/property.te
index a486702..56e02ef 100644
--- a/target/board/generic/sepolicy/property.te
+++ b/target/board/generic/sepolicy/property.te
@@ -1,4 +1,3 @@
type qemu_prop, property_type;
type qemu_cmdline, property_type;
type radio_noril_prop, property_type;
-type opengles_prop, property_type;
diff --git a/target/board/generic/sepolicy/property_contexts b/target/board/generic/sepolicy/property_contexts
index c66a85f..3a61b6b 100644
--- a/target/board/generic/sepolicy/property_contexts
+++ b/target/board/generic/sepolicy/property_contexts
@@ -3,4 +3,3 @@
ro.emu. u:object_r:qemu_prop:s0
ro.emulator. u:object_r:qemu_prop:s0
ro.radio.noril u:object_r:radio_noril_prop:s0
-ro.opengles. u:object_r:opengles_prop:s0
diff --git a/target/board/generic/sepolicy/system_server.te b/target/board/generic/sepolicy/system_server.te
index 9063095..dd70b12 100644
--- a/target/board/generic/sepolicy/system_server.te
+++ b/target/board/generic/sepolicy/system_server.te
@@ -1,2 +1 @@
-get_prop(system_server, opengles_prop)
get_prop(system_server, radio_noril_prop)
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index 6e7038e..f9030cf 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -56,6 +56,9 @@
PRODUCT_PACKAGES += \
cacerts \
+PRODUCT_PACKAGES += \
+ hiddenapi-package-whitelist.xml \
+
PRODUCT_SYSTEM_DEFAULT_PROPERTIES += \
dalvik.vm.image-dex2oat-Xms=64m \
dalvik.vm.image-dex2oat-Xmx=64m \
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index a22145a..6e3ef0a 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -144,6 +144,13 @@
--payload_signer_args <args>
Specify the arguments needed for payload signer.
+
+ --skip_postinstall
+ Skip the postinstall hooks when generating an A/B OTA package (default:
+ False). Note that this discards ALL the hooks, including non-optional
+ ones. Should only be used if caller knows it's safe to do so (e.g. all the
+ postinstall work is to dexopt apps and a data wipe will happen immediately
+ after). Only meaningful when generating A/B OTAs.
"""
from __future__ import print_function
@@ -151,6 +158,7 @@
import multiprocessing
import os.path
import shlex
+import shutil
import subprocess
import sys
import tempfile
@@ -193,8 +201,11 @@
OPTIONS.payload_signer_args = []
OPTIONS.extracted_input = None
OPTIONS.key_passwords = []
+OPTIONS.skip_postinstall = False
+
METADATA_NAME = 'META-INF/com/android/metadata'
+POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
UNZIP_PATTERN = ['IMAGES/*', 'META/*']
@@ -1215,7 +1226,7 @@
WriteMetadata(metadata, output_zip)
-def GetTargetFilesZipForSecondaryImages(input_file):
+def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
"""Returns a target-files.zip file for generating secondary payload.
Although the original target-files.zip already contains secondary slot
@@ -1229,6 +1240,7 @@
Args:
input_file: The input target-files.zip file.
+ skip_postinstall: Whether to skip copying the postinstall config file.
Returns:
The filename of the target-files.zip for generating secondary payload.
@@ -1247,6 +1259,10 @@
'IMAGES/system.map'):
pass
+ # Skip copying the postinstall config if requested.
+ elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
+ pass
+
elif info.filename.startswith(('META/', 'IMAGES/')):
common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
@@ -1256,6 +1272,31 @@
return target_file
+def GetTargetFilesZipWithoutPostinstallConfig(input_file):
+ """Returns a target-files.zip that's not containing postinstall_config.txt.
+
+ This allows brillo_update_payload script to skip writing all the postinstall
+ hooks in the generated payload. The input target-files.zip file will be
+ duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
+ contain the postinstall_config.txt entry, the input file will be returned.
+
+ Args:
+ input_file: The input target-files.zip filename.
+
+ Returns:
+ The filename of target-files.zip that doesn't contain postinstall config.
+ """
+ # We should only make a copy if postinstall_config entry exists.
+ with zipfile.ZipFile(input_file, 'r') as input_zip:
+ if POSTINSTALL_CONFIG not in input_zip.namelist():
+ return input_file
+
+ target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
+ shutil.copyfile(input_file, target_file)
+ common.ZipDelete(target_file, POSTINSTALL_CONFIG)
+ return target_file
+
+
def WriteABOTAPackageWithBrilloScript(target_file, output_file,
source_file=None):
"""Generate an Android OTA package that has A/B update payload."""
@@ -1325,6 +1366,9 @@
# Metadata to comply with Android OTA package format.
metadata = GetPackageMetadata(target_info, source_info)
+ if OPTIONS.skip_postinstall:
+ target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
+
# Generate payload.
payload = Payload()
payload.Generate(target_file, source_file)
@@ -1341,7 +1385,8 @@
if OPTIONS.include_secondary:
# We always include a full payload for the secondary slot, even when
# building an incremental OTA. See the comments for "--include_secondary".
- secondary_target_file = GetTargetFilesZipForSecondaryImages(target_file)
+ secondary_target_file = GetTargetFilesZipForSecondaryImages(
+ target_file, OPTIONS.skip_postinstall)
secondary_payload = Payload(secondary=True)
secondary_payload.Generate(secondary_target_file)
secondary_payload.Sign(payload_signer)
@@ -1469,6 +1514,8 @@
OPTIONS.payload_signer_args = shlex.split(a)
elif o == "--extracted_input_target_files":
OPTIONS.extracted_input = a
+ elif o == "--skip_postinstall":
+ OPTIONS.skip_postinstall = True
else:
return False
return True
@@ -1498,6 +1545,7 @@
"payload_signer=",
"payload_signer_args=",
"extracted_input_target_files=",
+ "skip_postinstall",
], extra_option_handler=option_handler)
if len(args) != 2:
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index c96e616..fa62c8f 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -104,6 +104,7 @@
import sys
import tempfile
import zipfile
+from xml.etree import ElementTree
import add_img_to_target_files
import common
@@ -290,6 +291,8 @@
new_data = RewriteProps(data)
common.ZipWriteStr(output_tf_zip, out_info, new_data)
+ # Replace the certs in *mac_permissions.xml (there could be multiple, such
+ # as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
elif info.filename.endswith("mac_permissions.xml"):
print("Rewriting %s with new keys." % (info.filename,))
new_data = ReplaceCerts(data)
@@ -361,31 +364,54 @@
def ReplaceCerts(data):
- """Given a string of data, replace all occurences of a set
- of X509 certs with a newer set of X509 certs and return
- the updated data string."""
- for old, new in OPTIONS.key_map.iteritems():
- try:
- if OPTIONS.verbose:
- print(" Replacing %s.x509.pem with %s.x509.pem" % (old, new))
- f = open(old + ".x509.pem")
- old_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower()
- f.close()
- f = open(new + ".x509.pem")
- new_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower()
- f.close()
- # Only match entire certs.
- pattern = "\\b" + old_cert16 + "\\b"
- (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
- if OPTIONS.verbose:
- print(" Replaced %d occurence(s) of %s.x509.pem with "
- "%s.x509.pem" % (num, old, new))
- except IOError as e:
- if e.errno == errno.ENOENT and not OPTIONS.verbose:
- continue
+ """Replaces all the occurences of X.509 certs with the new ones.
- print(" Error accessing %s. %s. Skip replacing %s.x509.pem with "
- "%s.x509.pem." % (e.filename, e.strerror, old, new))
+ The mapping info is read from OPTIONS.key_map. Non-existent certificate will
+ be skipped. After the replacement, it additionally checks for duplicate
+ entries, which would otherwise fail the policy loading code in
+ frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java.
+
+ Args:
+ data: Input string that contains a set of X.509 certs.
+
+ Returns:
+ A string after the replacement.
+
+ Raises:
+ AssertionError: On finding duplicate entries.
+ """
+ for old, new in OPTIONS.key_map.iteritems():
+ if OPTIONS.verbose:
+ print(" Replacing %s.x509.pem with %s.x509.pem" % (old, new))
+
+ try:
+ with open(old + ".x509.pem") as old_fp:
+ old_cert16 = base64.b16encode(
+ common.ParseCertificate(old_fp.read())).lower()
+ with open(new + ".x509.pem") as new_fp:
+ new_cert16 = base64.b16encode(
+ common.ParseCertificate(new_fp.read())).lower()
+ except IOError as e:
+ if OPTIONS.verbose or e.errno != errno.ENOENT:
+ print(" Error accessing %s: %s.\nSkip replacing %s.x509.pem with "
+ "%s.x509.pem." % (e.filename, e.strerror, old, new))
+ continue
+
+ # Only match entire certs.
+ pattern = "\\b" + old_cert16 + "\\b"
+ (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
+
+ if OPTIONS.verbose:
+ print(" Replaced %d occurence(s) of %s.x509.pem with %s.x509.pem" % (
+ num, old, new))
+
+ # Verify that there're no duplicate entries after the replacement. Note that
+ # it's only checking entries with global seinfo at the moment (i.e. ignoring
+ # the ones with inner packages). (Bug: 69479366)
+ root = ElementTree.fromstring(data)
+ signatures = [signer.attrib['signature'] for signer in root.findall('signer')]
+ assert len(signatures) == len(set(signatures)), \
+ "Found duplicate entries after cert replacement: {}".format(data)
return data
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index a4fa4f9..ee5bc53 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -24,7 +24,9 @@
import test_utils
from ota_from_target_files import (
_LoadOemDicts, BuildInfo, GetPackageMetadata,
- GetTargetFilesZipForSecondaryImages, Payload, PayloadSigner,
+ GetTargetFilesZipForSecondaryImages,
+ GetTargetFilesZipWithoutPostinstallConfig,
+ Payload, PayloadSigner, POSTINSTALL_CONFIG,
WriteFingerprintAssertion)
@@ -37,6 +39,16 @@
'META/update_engine_config.txt',
"PAYLOAD_MAJOR_VERSION=2\nPAYLOAD_MINOR_VERSION=4\n")
+ # META/postinstall_config.txt
+ target_files_zip.writestr(
+ POSTINSTALL_CONFIG,
+ '\n'.join([
+ "RUN_POSTINSTALL_system=true",
+ "POSTINSTALL_PATH_system=system/bin/otapreopt_script",
+ "FILESYSTEM_TYPE_system=ext4",
+ "POSTINSTALL_OPTIONAL_system=true",
+ ]))
+
# META/ab_partitions.txt
ab_partitions = ['boot', 'system', 'vendor']
target_files_zip.writestr(
@@ -539,10 +551,41 @@
self.assertIn('IMAGES/boot.img', namelist)
self.assertIn('IMAGES/system.img', namelist)
self.assertIn('IMAGES/vendor.img', namelist)
+ self.assertIn(POSTINSTALL_CONFIG, namelist)
self.assertNotIn('IMAGES/system_other.img', namelist)
self.assertNotIn('IMAGES/system.map', namelist)
+ def test_GetTargetFilesZipForSecondaryImages_skipPostinstall(self):
+ input_file = construct_target_files(secondary=True)
+ target_file = GetTargetFilesZipForSecondaryImages(
+ input_file, skip_postinstall=True)
+
+ with zipfile.ZipFile(target_file) as verify_zip:
+ namelist = verify_zip.namelist()
+
+ self.assertIn('META/ab_partitions.txt', namelist)
+ self.assertIn('IMAGES/boot.img', namelist)
+ self.assertIn('IMAGES/system.img', namelist)
+ self.assertIn('IMAGES/vendor.img', namelist)
+
+ self.assertNotIn('IMAGES/system_other.img', namelist)
+ self.assertNotIn('IMAGES/system.map', namelist)
+ self.assertNotIn(POSTINSTALL_CONFIG, namelist)
+
+ def test_GetTargetFilesZipWithoutPostinstallConfig(self):
+ input_file = construct_target_files()
+ target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
+ with zipfile.ZipFile(target_file) as verify_zip:
+ self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
+
+ def test_GetTargetFilesZipWithoutPostinstallConfig_missingEntry(self):
+ input_file = construct_target_files()
+ common.ZipDelete(input_file, POSTINSTALL_CONFIG)
+ target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
+ with zipfile.ZipFile(target_file) as verify_zip:
+ self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
+
class PayloadSignerTest(unittest.TestCase):
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index 0cab801..26f9e10 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -16,17 +16,25 @@
from __future__ import print_function
+import base64
import os.path
import unittest
import zipfile
import common
import test_utils
-from sign_target_files_apks import EditTags, ReplaceVerityKeyId, RewriteProps
+from sign_target_files_apks import (
+ EditTags, ReplaceCerts, ReplaceVerityKeyId, RewriteProps)
class SignTargetFilesApksTest(unittest.TestCase):
+ MAC_PERMISSIONS_XML = """<?xml version="1.0" encoding="iso-8859-1"?>
+<policy>
+ <signer signature="{}"><seinfo value="platform"/></signer>
+ <signer signature="{}"><seinfo value="media"/></signer>
+</policy>"""
+
def setUp(self):
self.testdata_dir = test_utils.get_testdata_dir()
@@ -133,3 +141,73 @@
with zipfile.ZipFile(output_file) as output_zip:
self.assertEqual(BOOT_CMDLINE, output_zip.read('BOOT/cmdline'))
+
+ def test_ReplaceCerts(self):
+ cert1_path = os.path.join(self.testdata_dir, 'platform.x509.pem')
+ with open(cert1_path) as cert1_fp:
+ cert1 = cert1_fp.read()
+ cert2_path = os.path.join(self.testdata_dir, 'media.x509.pem')
+ with open(cert2_path) as cert2_fp:
+ cert2 = cert2_fp.read()
+ cert3_path = os.path.join(self.testdata_dir, 'testkey.x509.pem')
+ with open(cert3_path) as cert3_fp:
+ cert3 = cert3_fp.read()
+
+ # Replace cert1 with cert3.
+ input_xml = self.MAC_PERMISSIONS_XML.format(
+ base64.b16encode(common.ParseCertificate(cert1)).lower(),
+ base64.b16encode(common.ParseCertificate(cert2)).lower())
+
+ output_xml = self.MAC_PERMISSIONS_XML.format(
+ base64.b16encode(common.ParseCertificate(cert3)).lower(),
+ base64.b16encode(common.ParseCertificate(cert2)).lower())
+
+ common.OPTIONS.key_map = {
+ cert1_path[:-9] : cert3_path[:-9],
+ }
+
+ self.assertEqual(output_xml, ReplaceCerts(input_xml))
+
+ def test_ReplaceCerts_duplicateEntries(self):
+ cert1_path = os.path.join(self.testdata_dir, 'platform.x509.pem')
+ with open(cert1_path) as cert1_fp:
+ cert1 = cert1_fp.read()
+ cert2_path = os.path.join(self.testdata_dir, 'media.x509.pem')
+ with open(cert2_path) as cert2_fp:
+ cert2 = cert2_fp.read()
+
+ # Replace cert1 with cert2, which leads to duplicate entries.
+ input_xml = self.MAC_PERMISSIONS_XML.format(
+ base64.b16encode(common.ParseCertificate(cert1)).lower(),
+ base64.b16encode(common.ParseCertificate(cert2)).lower())
+
+ common.OPTIONS.key_map = {
+ cert1_path[:-9] : cert2_path[:-9],
+ }
+ self.assertRaises(AssertionError, ReplaceCerts, input_xml)
+
+ def test_ReplaceCerts_skipNonExistentCerts(self):
+ cert1_path = os.path.join(self.testdata_dir, 'platform.x509.pem')
+ with open(cert1_path) as cert1_fp:
+ cert1 = cert1_fp.read()
+ cert2_path = os.path.join(self.testdata_dir, 'media.x509.pem')
+ with open(cert2_path) as cert2_fp:
+ cert2 = cert2_fp.read()
+ cert3_path = os.path.join(self.testdata_dir, 'testkey.x509.pem')
+ with open(cert3_path) as cert3_fp:
+ cert3 = cert3_fp.read()
+
+ input_xml = self.MAC_PERMISSIONS_XML.format(
+ base64.b16encode(common.ParseCertificate(cert1)).lower(),
+ base64.b16encode(common.ParseCertificate(cert2)).lower())
+
+ output_xml = self.MAC_PERMISSIONS_XML.format(
+ base64.b16encode(common.ParseCertificate(cert3)).lower(),
+ base64.b16encode(common.ParseCertificate(cert2)).lower())
+
+ common.OPTIONS.key_map = {
+ cert1_path[:-9] : cert3_path[:-9],
+ 'non-existent' : cert3_path[:-9],
+ cert2_path[:-9] : 'non-existent',
+ }
+ self.assertEqual(output_xml, ReplaceCerts(input_xml))
diff --git a/tools/releasetools/testdata/media.x509.pem b/tools/releasetools/testdata/media.x509.pem
new file mode 100644
index 0000000..98cd443
--- /dev/null
+++ b/tools/releasetools/testdata/media.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAPK5jmEjVyxOMA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0wODA0MTUyMzQwNTdaFw0zNTA5MDEyMzQwNTdaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBAK4lDFoW75f8KGmsZRsyF8w2ug6GlkFo1YoE
+n0DOhYZxI6P/tPbZScM88to6BcI+rKpX2AOImxdZvPWefG8hiQriUIW37VaqYmwJ
+ie+czTY2LKDo0blgP9TYModnkmzMCQxot3Wuf/MJNMw2nvKFWiZn3wxmf9DHz12O
+umVYBnNzA7tiRybquu37cvB+16dqs8uaOBxLfc2AmxQNiR8AITvkAfWNagamHq3D
+qcLxxlZyhbCa4JNCpm+kIer5Ot91c6AowzHXBgGrOvfMhAM+znx3KjpbhrDb6dd3
+w6SKqYAe3O4ngVifRNnkETl5YAV2qZQQuoEJElna2YxsaP94S48CAQOjgfwwgfkw
+HQYDVR0OBBYEFMopPKqLwO0+VC7vQgWiv/K1fk11MIHJBgNVHSMEgcEwgb6AFMop
+PKqLwO0+VC7vQgWiv/K1fk11oYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAPK5jmEjVyxOMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAITelRbV5KhyF6c9qEhwSPUzc6X3
+M/OQ1hvfPMnlJRYlv8qnwxWcriddFyqa4eh21UWBJ6xUL2gpDdUQwAKdj1Hg7hVr
+e3tazbOUJBuOx4t05cQsXK+uFWyvW9GZojonUk2gct6743hGSlM2MLDk0P+34I7L
+cB+ttjecdEZ/bgDG7YiFlTgHkgOHVgB4csjjAHr0I6V6LKs6KChptkxLe9X8GH0K
+fiQVll1ark4Hpt91G0p16Xk8kYphK4HNC2KK7gFo3ETkexDTWTJghJ1q321yfcJE
+RMIh0/nsw2jK0HmZ8rgQW8HyDTjUEGbMFBHCV6lupDSfV0ZWVQfk6AIKGoE=
+-----END CERTIFICATE-----
diff --git a/tools/releasetools/testdata/platform.x509.pem b/tools/releasetools/testdata/platform.x509.pem
new file mode 100644
index 0000000..087f02e
--- /dev/null
+++ b/tools/releasetools/testdata/platform.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJALOZgIbQVs/6MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0wODA0MTUyMjQwNTBaFw0zNTA5MDEyMjQwNTBaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBAJx4BZKsDV04HN6qZezIpgBuNkgMbXIHsSAR
+vlCGOqvitV0Amt9xRtbyICKAx81Ne9smJDuKgGwms0sTdSOkkmgiSQTcAUk+fArP
+GgXIdPabA3tgMJ2QdNJCgOFrrSqHNDYZUer3KkgtCbIEsYdeEqyYwap3PWgAuer9
+5W1Yvtjo2hb5o2AJnDeoNKbf7be2tEoEngeiafzPLFSW8s821k35CjuNjzSjuqtM
+9TNxqydxmzulh1StDFP8FOHbRdUeI0+76TybpO35zlQmE1DsU1YHv2mi/0qgfbX3
+6iANCabBtJ4hQC+J7RGQiTqrWpGA8VLoL4WkV1PPX8GQccXuyCcCAQOjgfwwgfkw
+HQYDVR0OBBYEFE/koLPdnLop9x1yh8Tnw48ghsKZMIHJBgNVHSMEgcEwgb6AFE/k
+oLPdnLop9x1yh8Tnw48ghsKZoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJALOZgIbQVs/6MAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAFclUbjZOh9z3g9tRp+G2tZwFAAp
+PIigzXzXeLc9r8wZf6t25iEuVsHHYc/EL9cz3lLFCuCIFM78CjtaGkNGBU2Cnx2C
+tCsgSL+ItdFJKe+F9g7dEtctVWV+IuPoXQTIMdYT0Zk4u4mCJH+jISVroS0dao+S
+6h2xw3Mxe6DAN/DRr/ZFrvIkl5+6bnoUvAJccbmBOM7z3fwFlhfPJIRc97QNY4L3
+J17XOElatuWTG5QhdlxJG3L7aOCA29tYwgKdNHyLMozkPvaosVUz7fvpib1qSN1L
+IC7alMarjdW4OZID2q4u1EYjLk/pvZYTlMYwDlE448/Shebk5INTjLixs1c=
+-----END CERTIFICATE-----