1# 2# Copyright (C) 2018 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import os 18import os.path 19import tempfile 20import zipfile 21 22import common 23import test_utils 24from add_img_to_target_files import ( 25 AddPackRadioImages, 26 AddCareMapForAbOta, GetCareMap, 27 CheckAbOtaImages) 28from rangelib import RangeSet 29 30 31OPTIONS = common.OPTIONS 32 33 34class AddImagesToTargetFilesTest(test_utils.ReleaseToolsTestCase): 35 36 def setUp(self): 37 OPTIONS.input_tmp = common.MakeTempDir() 38 39 @staticmethod 40 def _create_images(images, prefix): 41 """Creates images under OPTIONS.input_tmp/prefix.""" 42 path = os.path.join(OPTIONS.input_tmp, prefix) 43 if not os.path.exists(path): 44 os.mkdir(path) 45 46 for image in images: 47 image_path = os.path.join(path, image + '.img') 48 with open(image_path, 'wb') as image_fp: 49 image_fp.write(image.encode()) 50 51 images_path = os.path.join(OPTIONS.input_tmp, 'IMAGES') 52 if not os.path.exists(images_path): 53 os.mkdir(images_path) 54 return images, images_path 55 56 def test_CheckAbOtaImages_imageExistsUnderImages(self): 57 """Tests the case with existing images under IMAGES/.""" 58 images, _ = self._create_images(['aboot', 'xbl'], 'IMAGES') 59 CheckAbOtaImages(None, images) 60 61 def test_CheckAbOtaImages_imageExistsUnderRadio(self): 62 """Tests the case with some image under RADIO/.""" 63 images, _ = self._create_images(['system', 'vendor'], 'IMAGES') 64 radio_path = os.path.join(OPTIONS.input_tmp, 'RADIO') 65 if not os.path.exists(radio_path): 66 os.mkdir(radio_path) 67 with open(os.path.join(radio_path, 'modem.img'), 'wb') as image_fp: 68 image_fp.write('modem'.encode()) 69 CheckAbOtaImages(None, images + ['modem']) 70 71 def test_CheckAbOtaImages_missingImages(self): 72 images, _ = self._create_images(['aboot', 'xbl'], 'RADIO') 73 self.assertRaises( 74 AssertionError, CheckAbOtaImages, None, images + ['baz']) 75 76 def test_AddPackRadioImages(self): 77 images, images_path = self._create_images(['foo', 'bar'], 'RADIO') 78 AddPackRadioImages(None, images) 79 80 for image in images: 81 self.assertTrue( 82 os.path.exists(os.path.join(images_path, image + '.img'))) 83 84 def test_AddPackRadioImages_with_suffix(self): 85 images, images_path = self._create_images(['foo', 'bar'], 'RADIO') 86 images_with_suffix = [image + '.img' for image in images] 87 AddPackRadioImages(None, images_with_suffix) 88 89 for image in images: 90 self.assertTrue( 91 os.path.exists(os.path.join(images_path, image + '.img'))) 92 93 def test_AddPackRadioImages_zipOutput(self): 94 images, _ = self._create_images(['foo', 'bar'], 'RADIO') 95 96 # Set up the output zip. 97 output_file = common.MakeTempFile(suffix='.zip') 98 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 99 AddPackRadioImages(output_zip, images) 100 101 with zipfile.ZipFile(output_file, 'r', allowZip64=True) as verify_zip: 102 for image in images: 103 self.assertIn('IMAGES/' + image + '.img', verify_zip.namelist()) 104 105 def test_AddPackRadioImages_imageExists(self): 106 images, images_path = self._create_images(['foo', 'bar'], 'RADIO') 107 108 # Additionally create images under IMAGES/ so that they should be skipped. 109 images, images_path = self._create_images(['foo', 'bar'], 'IMAGES') 110 111 AddPackRadioImages(None, images) 112 113 for image in images: 114 self.assertTrue( 115 os.path.exists(os.path.join(images_path, image + '.img'))) 116 117 def test_AddPackRadioImages_missingImages(self): 118 images, _ = self._create_images(['foo', 'bar'], 'RADIO') 119 AddPackRadioImages(None, images) 120 121 self.assertRaises(AssertionError, AddPackRadioImages, None, 122 images + ['baz']) 123 124 @staticmethod 125 def _test_AddCareMapForAbOta(): 126 """Helper function to set up the test for test_AddCareMapForAbOta().""" 127 OPTIONS.info_dict = { 128 'system_verity_block_device': '/dev/block/system', 129 'vendor_verity_block_device': '/dev/block/vendor', 130 'system.build.prop': common.PartitionBuildProps.FromDictionary( 131 'system', { 132 'ro.system.build.fingerprint': 133 'google/sailfish/12345:user/dev-keys'} 134 ), 135 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( 136 'vendor', { 137 'ro.vendor.build.fingerprint': 138 'google/sailfish/678:user/dev-keys'} 139 ), 140 } 141 142 # Prepare the META/ folder. 143 meta_path = os.path.join(OPTIONS.input_tmp, 'META') 144 if not os.path.exists(meta_path): 145 os.mkdir(meta_path) 146 147 system_image = test_utils.construct_sparse_image([ 148 (0xCAC1, 6), 149 (0xCAC3, 4), 150 (0xCAC1, 6)], "system") 151 vendor_image = test_utils.construct_sparse_image([ 152 (0xCAC2, 10)], "vendor") 153 154 image_paths = { 155 'system': system_image, 156 'vendor': vendor_image, 157 } 158 return image_paths 159 160 def _verifyCareMap(self, expected, file_name): 161 """Parses the care_map.pb; and checks the content in plain text.""" 162 text_file = common.MakeTempFile(prefix="caremap-", suffix=".txt") 163 164 # Calls an external binary to convert the proto message. 165 cmd = ["care_map_generator", "--parse_proto", file_name, text_file] 166 common.RunAndCheckOutput(cmd) 167 168 with open(text_file) as verify_fp: 169 plain_text = verify_fp.read() 170 self.assertEqual('\n'.join(expected), plain_text) 171 172 @test_utils.SkipIfExternalToolsUnavailable() 173 def test_AddCareMapForAbOta(self): 174 image_paths = self._test_AddCareMapForAbOta() 175 176 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 177 AddCareMapForAbOta(care_map_file, ['system', 'vendor'], image_paths) 178 179 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), 180 "ro.system.build.fingerprint", 181 "google/sailfish/12345:user/dev-keys", 182 'vendor', RangeSet("0-9").to_string_raw(), 183 "ro.vendor.build.fingerprint", 184 "google/sailfish/678:user/dev-keys"] 185 186 self._verifyCareMap(expected, care_map_file) 187 188 @test_utils.SkipIfExternalToolsUnavailable() 189 def test_AddCareMapForAbOta_withNonCareMapPartitions(self): 190 """Partitions without care_map should be ignored.""" 191 image_paths = self._test_AddCareMapForAbOta() 192 193 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 194 AddCareMapForAbOta( 195 care_map_file, ['boot', 'system', 'vendor', 'vbmeta'], image_paths) 196 197 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), 198 "ro.system.build.fingerprint", 199 "google/sailfish/12345:user/dev-keys", 200 'vendor', RangeSet("0-9").to_string_raw(), 201 "ro.vendor.build.fingerprint", 202 "google/sailfish/678:user/dev-keys"] 203 204 self._verifyCareMap(expected, care_map_file) 205 206 @test_utils.SkipIfExternalToolsUnavailable() 207 def test_AddCareMapForAbOta_withAvb(self): 208 """Tests the case for device using AVB.""" 209 image_paths = self._test_AddCareMapForAbOta() 210 OPTIONS.info_dict = { 211 'avb_system_hashtree_enable': 'true', 212 'avb_vendor_hashtree_enable': 'true', 213 'system.build.prop': common.PartitionBuildProps.FromDictionary( 214 'system', { 215 'ro.system.build.fingerprint': 216 'google/sailfish/12345:user/dev-keys'} 217 ), 218 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( 219 'vendor', { 220 'ro.vendor.build.fingerprint': 221 'google/sailfish/678:user/dev-keys'} 222 ), 223 } 224 225 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 226 AddCareMapForAbOta(care_map_file, ['system', 'vendor'], image_paths) 227 228 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), 229 "ro.system.build.fingerprint", 230 "google/sailfish/12345:user/dev-keys", 231 'vendor', RangeSet("0-9").to_string_raw(), 232 "ro.vendor.build.fingerprint", 233 "google/sailfish/678:user/dev-keys"] 234 235 self._verifyCareMap(expected, care_map_file) 236 237 @test_utils.SkipIfExternalToolsUnavailable() 238 def test_AddCareMapForAbOta_noFingerprint(self): 239 """Tests the case for partitions without fingerprint.""" 240 image_paths = self._test_AddCareMapForAbOta() 241 OPTIONS.info_dict = { 242 'system_verity_block_device': '/dev/block/system', 243 'vendor_verity_block_device': '/dev/block/vendor', 244 } 245 246 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 247 AddCareMapForAbOta(care_map_file, ['system', 'vendor'], image_paths) 248 249 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), "unknown", 250 "unknown", 'vendor', RangeSet( 251 "0-9").to_string_raw(), "unknown", 252 "unknown"] 253 254 self._verifyCareMap(expected, care_map_file) 255 256 @test_utils.SkipIfExternalToolsUnavailable() 257 def test_AddCareMapForAbOta_withThumbprint(self): 258 """Tests the case for partitions with thumbprint.""" 259 image_paths = self._test_AddCareMapForAbOta() 260 OPTIONS.info_dict = { 261 'system_verity_block_device': '/dev/block/system', 262 'vendor_verity_block_device': '/dev/block/vendor', 263 'system.build.prop': common.PartitionBuildProps.FromDictionary( 264 'system', { 265 'ro.system.build.thumbprint': 266 'google/sailfish/123:user/dev-keys'} 267 ), 268 'vendor.build.prop': common.PartitionBuildProps.FromDictionary( 269 'vendor', { 270 'ro.vendor.build.thumbprint': 271 'google/sailfish/456:user/dev-keys'} 272 ), 273 } 274 275 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 276 AddCareMapForAbOta(care_map_file, ['system', 'vendor'], image_paths) 277 278 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), 279 "ro.system.build.thumbprint", 280 "google/sailfish/123:user/dev-keys", 281 'vendor', RangeSet("0-9").to_string_raw(), 282 "ro.vendor.build.thumbprint", 283 "google/sailfish/456:user/dev-keys"] 284 285 self._verifyCareMap(expected, care_map_file) 286 287 @test_utils.SkipIfExternalToolsUnavailable() 288 def test_AddCareMapForAbOta_skipPartition(self): 289 image_paths = self._test_AddCareMapForAbOta() 290 test_utils.erase_avb_footer(image_paths["vendor"]) 291 292 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 293 AddCareMapForAbOta(care_map_file, ['system', 'vendor'], image_paths) 294 295 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), 296 "ro.system.build.fingerprint", 297 "google/sailfish/12345:user/dev-keys"] 298 299 self._verifyCareMap(expected, care_map_file) 300 301 @test_utils.SkipIfExternalToolsUnavailable() 302 def test_AddCareMapForAbOta_skipAllPartitions(self): 303 image_paths = self._test_AddCareMapForAbOta() 304 test_utils.erase_avb_footer(image_paths["system"]) 305 test_utils.erase_avb_footer(image_paths["vendor"]) 306 307 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 308 AddCareMapForAbOta(care_map_file, ['system', 'vendor'], image_paths) 309 310 self.assertFalse(os.path.exists(care_map_file)) 311 312 def test_AddCareMapForAbOta_verityNotEnabled(self): 313 """No care_map.pb should be generated if verity not enabled.""" 314 image_paths = self._test_AddCareMapForAbOta() 315 OPTIONS.info_dict = {} 316 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 317 AddCareMapForAbOta(care_map_file, ['system', 'vendor'], image_paths) 318 319 self.assertFalse(os.path.exists(care_map_file)) 320 321 def test_AddCareMapForAbOta_missingImageFile(self): 322 """Missing image file should be considered fatal.""" 323 image_paths = self._test_AddCareMapForAbOta() 324 image_paths['vendor'] = '' 325 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 326 self.assertRaises(common.ExternalError, AddCareMapForAbOta, care_map_file, 327 ['system', 'vendor'], image_paths) 328 329 @test_utils.SkipIfExternalToolsUnavailable() 330 def test_AddCareMapForAbOta_zipOutput(self): 331 """Tests the case with ZIP output.""" 332 image_paths = self._test_AddCareMapForAbOta() 333 334 output_file = common.MakeTempFile(suffix='.zip') 335 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 336 AddCareMapForAbOta(output_zip, ['system', 'vendor'], image_paths) 337 338 care_map_name = "META/care_map.pb" 339 temp_dir = common.MakeTempDir() 340 with zipfile.ZipFile(output_file, 'r', allowZip64=True) as verify_zip: 341 self.assertTrue(care_map_name in verify_zip.namelist()) 342 verify_zip.extract(care_map_name, path=temp_dir) 343 344 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), 345 "ro.system.build.fingerprint", 346 "google/sailfish/12345:user/dev-keys", 347 'vendor', RangeSet("0-9").to_string_raw(), 348 "ro.vendor.build.fingerprint", 349 "google/sailfish/678:user/dev-keys"] 350 self._verifyCareMap(expected, os.path.join(temp_dir, care_map_name)) 351 352 @test_utils.SkipIfExternalToolsUnavailable() 353 def test_AddCareMapForAbOta_zipOutput_careMapEntryExists(self): 354 """Tests the case with ZIP output which already has care_map entry.""" 355 image_paths = self._test_AddCareMapForAbOta() 356 357 output_file = common.MakeTempFile(suffix='.zip') 358 with zipfile.ZipFile(output_file, 'w', allowZip64=True) as output_zip: 359 # Create an existing META/care_map.pb entry. 360 common.ZipWriteStr(output_zip, 'META/care_map.pb', 361 'fake care_map.pb') 362 363 # Request to add META/care_map.pb again. 364 AddCareMapForAbOta(output_zip, ['system', 'vendor'], image_paths) 365 366 # The one under OPTIONS.input_tmp must have been replaced. 367 care_map_file = os.path.join(OPTIONS.input_tmp, 'META', 'care_map.pb') 368 expected = ['system', RangeSet("0-5 10-15").to_string_raw(), 369 "ro.system.build.fingerprint", 370 "google/sailfish/12345:user/dev-keys", 371 'vendor', RangeSet("0-9").to_string_raw(), 372 "ro.vendor.build.fingerprint", 373 "google/sailfish/678:user/dev-keys"] 374 375 self._verifyCareMap(expected, care_map_file) 376 377 # The existing entry should be scheduled to be replaced. 378 self.assertIn('META/care_map.pb', OPTIONS.replace_updated_files_list) 379 380 def test_GetCareMap(self): 381 sparse_image = test_utils.construct_sparse_image([ 382 (0xCAC1, 6), 383 (0xCAC3, 4), 384 (0xCAC1, 6)], "system") 385 name, care_map = GetCareMap('system', sparse_image) 386 self.assertEqual('system', name) 387 self.assertEqual(RangeSet("0-5 10-15").to_string_raw(), care_map) 388 389 def test_GetCareMap_invalidPartition(self): 390 self.assertRaises(AssertionError, GetCareMap, 'oem', None) 391 392 def test_GetCareMap_nonSparseImage(self): 393 with tempfile.NamedTemporaryFile() as tmpfile: 394 tmpfile.truncate(4096 * 13) 395 test_utils.append_avb_footer(tmpfile.name, "system") 396 name, care_map = GetCareMap('system', tmpfile.name) 397 self.assertEqual('system', name) 398 self.assertEqual(RangeSet("0-12").to_string_raw(), care_map) 399