blob: c8e87bff47e877771e7fe3cf7ee6ef64a6e65bc8 [file] [log] [blame]
Tao Bao481bab82017-12-21 11:23:09 -08001#
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 copy
Tao Baoc7b403a2018-01-30 18:19:04 -080018import os
Tao Baofabe0832018-01-17 15:52:28 -080019import os.path
Tao Bao481bab82017-12-21 11:23:09 -080020import unittest
Tao Baoc7b403a2018-01-30 18:19:04 -080021import zipfile
Tao Bao481bab82017-12-21 11:23:09 -080022
23import common
Tao Bao04e1f012018-02-04 12:13:35 -080024import test_utils
Tao Bao481bab82017-12-21 11:23:09 -080025from ota_from_target_files import (
Tao Baoae5e4c32018-03-01 19:30:00 -080026 _LoadOemDicts, BuildInfo, GetPackageMetadata,
Tao Bao15a146a2018-02-21 16:06:59 -080027 GetTargetFilesZipForSecondaryImages,
28 GetTargetFilesZipWithoutPostinstallConfig,
Tao Baoae5e4c32018-03-01 19:30:00 -080029 Payload, PayloadSigner, POSTINSTALL_CONFIG, StreamingPropertyFiles,
Tao Baofabe0832018-01-17 15:52:28 -080030 WriteFingerprintAssertion)
31
32
Tao Baof7140c02018-01-30 17:09:24 -080033def construct_target_files(secondary=False):
34 """Returns a target-files.zip file for generating OTA packages."""
35 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
36 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
37 # META/update_engine_config.txt
38 target_files_zip.writestr(
39 'META/update_engine_config.txt',
40 "PAYLOAD_MAJOR_VERSION=2\nPAYLOAD_MINOR_VERSION=4\n")
41
Tao Bao15a146a2018-02-21 16:06:59 -080042 # META/postinstall_config.txt
43 target_files_zip.writestr(
44 POSTINSTALL_CONFIG,
45 '\n'.join([
46 "RUN_POSTINSTALL_system=true",
47 "POSTINSTALL_PATH_system=system/bin/otapreopt_script",
48 "FILESYSTEM_TYPE_system=ext4",
49 "POSTINSTALL_OPTIONAL_system=true",
50 ]))
51
Tao Baof7140c02018-01-30 17:09:24 -080052 # META/ab_partitions.txt
53 ab_partitions = ['boot', 'system', 'vendor']
54 target_files_zip.writestr(
55 'META/ab_partitions.txt',
56 '\n'.join(ab_partitions))
57
58 # Create dummy images for each of them.
59 for partition in ab_partitions:
60 target_files_zip.writestr('IMAGES/' + partition + '.img',
61 os.urandom(len(partition)))
62
63 if secondary:
64 target_files_zip.writestr('IMAGES/system_other.img',
65 os.urandom(len("system_other")))
66
67 return target_files
68
69
Tao Bao481bab82017-12-21 11:23:09 -080070class MockScriptWriter(object):
71 """A class that mocks edify_generator.EdifyGenerator.
72
73 It simply pushes the incoming arguments onto script stack, which is to assert
74 the calls to EdifyGenerator functions.
75 """
76
77 def __init__(self):
78 self.script = []
79
80 def Mount(self, *args):
81 self.script.append(('Mount',) + args)
82
83 def AssertDevice(self, *args):
84 self.script.append(('AssertDevice',) + args)
85
86 def AssertOemProperty(self, *args):
87 self.script.append(('AssertOemProperty',) + args)
88
89 def AssertFingerprintOrThumbprint(self, *args):
90 self.script.append(('AssertFingerprintOrThumbprint',) + args)
91
92 def AssertSomeFingerprint(self, *args):
93 self.script.append(('AssertSomeFingerprint',) + args)
94
95 def AssertSomeThumbprint(self, *args):
96 self.script.append(('AssertSomeThumbprint',) + args)
97
98
99class BuildInfoTest(unittest.TestCase):
100
101 TEST_INFO_DICT = {
102 'build.prop' : {
103 'ro.product.device' : 'product-device',
104 'ro.product.name' : 'product-name',
105 'ro.build.fingerprint' : 'build-fingerprint',
106 'ro.build.foo' : 'build-foo',
107 },
108 'vendor.build.prop' : {
109 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
110 },
111 'property1' : 'value1',
112 'property2' : 4096,
113 }
114
115 TEST_INFO_DICT_USES_OEM_PROPS = {
116 'build.prop' : {
117 'ro.product.name' : 'product-name',
118 'ro.build.thumbprint' : 'build-thumbprint',
119 'ro.build.bar' : 'build-bar',
120 },
121 'vendor.build.prop' : {
122 'ro.vendor.build.fingerprint' : 'vendor-build-fingerprint',
123 },
124 'property1' : 'value1',
125 'property2' : 4096,
126 'oem_fingerprint_properties' : 'ro.product.device ro.product.brand',
127 }
128
129 TEST_OEM_DICTS = [
130 {
131 'ro.product.brand' : 'brand1',
132 'ro.product.device' : 'device1',
133 },
134 {
135 'ro.product.brand' : 'brand2',
136 'ro.product.device' : 'device2',
137 },
138 {
139 'ro.product.brand' : 'brand3',
140 'ro.product.device' : 'device3',
141 },
142 ]
143
144 def test_init(self):
145 target_info = BuildInfo(self.TEST_INFO_DICT, None)
146 self.assertEqual('product-device', target_info.device)
147 self.assertEqual('build-fingerprint', target_info.fingerprint)
148 self.assertFalse(target_info.is_ab)
149 self.assertIsNone(target_info.oem_props)
150
151 def test_init_with_oem_props(self):
152 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
153 self.TEST_OEM_DICTS)
154 self.assertEqual('device1', target_info.device)
155 self.assertEqual('brand1/product-name/device1:build-thumbprint',
156 target_info.fingerprint)
157
158 # Swap the order in oem_dicts, which would lead to different BuildInfo.
159 oem_dicts = copy.copy(self.TEST_OEM_DICTS)
160 oem_dicts[0], oem_dicts[2] = oem_dicts[2], oem_dicts[0]
161 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS, oem_dicts)
162 self.assertEqual('device3', target_info.device)
163 self.assertEqual('brand3/product-name/device3:build-thumbprint',
164 target_info.fingerprint)
165
166 # Missing oem_dict should be rejected.
167 self.assertRaises(AssertionError, BuildInfo,
168 self.TEST_INFO_DICT_USES_OEM_PROPS, None)
169
170 def test___getitem__(self):
171 target_info = BuildInfo(self.TEST_INFO_DICT, None)
172 self.assertEqual('value1', target_info['property1'])
173 self.assertEqual(4096, target_info['property2'])
174 self.assertEqual('build-foo', target_info['build.prop']['ro.build.foo'])
175
176 def test___getitem__with_oem_props(self):
177 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
178 self.TEST_OEM_DICTS)
179 self.assertEqual('value1', target_info['property1'])
180 self.assertEqual(4096, target_info['property2'])
181 self.assertRaises(KeyError,
182 lambda: target_info['build.prop']['ro.build.foo'])
183
184 def test_get(self):
185 target_info = BuildInfo(self.TEST_INFO_DICT, None)
186 self.assertEqual('value1', target_info.get('property1'))
187 self.assertEqual(4096, target_info.get('property2'))
188 self.assertEqual(4096, target_info.get('property2', 1024))
189 self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
190 self.assertEqual('build-foo', target_info.get('build.prop')['ro.build.foo'])
191
192 def test_get_with_oem_props(self):
193 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
194 self.TEST_OEM_DICTS)
195 self.assertEqual('value1', target_info.get('property1'))
196 self.assertEqual(4096, target_info.get('property2'))
197 self.assertEqual(4096, target_info.get('property2', 1024))
198 self.assertEqual(1024, target_info.get('property-nonexistent', 1024))
199 self.assertIsNone(target_info.get('build.prop').get('ro.build.foo'))
200 self.assertRaises(KeyError,
201 lambda: target_info.get('build.prop')['ro.build.foo'])
202
203 def test_GetBuildProp(self):
204 target_info = BuildInfo(self.TEST_INFO_DICT, None)
205 self.assertEqual('build-foo', target_info.GetBuildProp('ro.build.foo'))
206 self.assertRaises(common.ExternalError, target_info.GetBuildProp,
207 'ro.build.nonexistent')
208
209 def test_GetBuildProp_with_oem_props(self):
210 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
211 self.TEST_OEM_DICTS)
212 self.assertEqual('build-bar', target_info.GetBuildProp('ro.build.bar'))
213 self.assertRaises(common.ExternalError, target_info.GetBuildProp,
214 'ro.build.nonexistent')
215
216 def test_GetVendorBuildProp(self):
217 target_info = BuildInfo(self.TEST_INFO_DICT, None)
218 self.assertEqual('vendor-build-fingerprint',
219 target_info.GetVendorBuildProp(
220 'ro.vendor.build.fingerprint'))
221 self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
222 'ro.build.nonexistent')
223
224 def test_GetVendorBuildProp_with_oem_props(self):
225 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
226 self.TEST_OEM_DICTS)
227 self.assertEqual('vendor-build-fingerprint',
228 target_info.GetVendorBuildProp(
229 'ro.vendor.build.fingerprint'))
230 self.assertRaises(common.ExternalError, target_info.GetVendorBuildProp,
231 'ro.build.nonexistent')
232
233 def test_WriteMountOemScript(self):
234 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
235 self.TEST_OEM_DICTS)
236 script_writer = MockScriptWriter()
237 target_info.WriteMountOemScript(script_writer)
238 self.assertEqual([('Mount', '/oem', None)], script_writer.script)
239
240 def test_WriteDeviceAssertions(self):
241 target_info = BuildInfo(self.TEST_INFO_DICT, None)
242 script_writer = MockScriptWriter()
243 target_info.WriteDeviceAssertions(script_writer, False)
244 self.assertEqual([('AssertDevice', 'product-device')], script_writer.script)
245
246 def test_WriteDeviceAssertions_with_oem_props(self):
247 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
248 self.TEST_OEM_DICTS)
249 script_writer = MockScriptWriter()
250 target_info.WriteDeviceAssertions(script_writer, False)
251 self.assertEqual(
252 [
253 ('AssertOemProperty', 'ro.product.device',
254 ['device1', 'device2', 'device3'], False),
255 ('AssertOemProperty', 'ro.product.brand',
256 ['brand1', 'brand2', 'brand3'], False),
257 ],
258 script_writer.script)
259
260 def test_WriteFingerprintAssertion_without_oem_props(self):
261 target_info = BuildInfo(self.TEST_INFO_DICT, None)
262 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT)
263 source_info_dict['build.prop']['ro.build.fingerprint'] = (
264 'source-build-fingerprint')
265 source_info = BuildInfo(source_info_dict, None)
266
267 script_writer = MockScriptWriter()
268 WriteFingerprintAssertion(script_writer, target_info, source_info)
269 self.assertEqual(
270 [('AssertSomeFingerprint', 'source-build-fingerprint',
271 'build-fingerprint')],
272 script_writer.script)
273
274 def test_WriteFingerprintAssertion_with_source_oem_props(self):
275 target_info = BuildInfo(self.TEST_INFO_DICT, None)
276 source_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
277 self.TEST_OEM_DICTS)
278
279 script_writer = MockScriptWriter()
280 WriteFingerprintAssertion(script_writer, target_info, source_info)
281 self.assertEqual(
282 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
283 'build-thumbprint')],
284 script_writer.script)
285
286 def test_WriteFingerprintAssertion_with_target_oem_props(self):
287 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
288 self.TEST_OEM_DICTS)
289 source_info = BuildInfo(self.TEST_INFO_DICT, None)
290
291 script_writer = MockScriptWriter()
292 WriteFingerprintAssertion(script_writer, target_info, source_info)
293 self.assertEqual(
294 [('AssertFingerprintOrThumbprint', 'build-fingerprint',
295 'build-thumbprint')],
296 script_writer.script)
297
298 def test_WriteFingerprintAssertion_with_both_oem_props(self):
299 target_info = BuildInfo(self.TEST_INFO_DICT_USES_OEM_PROPS,
300 self.TEST_OEM_DICTS)
301 source_info_dict = copy.deepcopy(self.TEST_INFO_DICT_USES_OEM_PROPS)
302 source_info_dict['build.prop']['ro.build.thumbprint'] = (
303 'source-build-thumbprint')
304 source_info = BuildInfo(source_info_dict, self.TEST_OEM_DICTS)
305
306 script_writer = MockScriptWriter()
307 WriteFingerprintAssertion(script_writer, target_info, source_info)
308 self.assertEqual(
309 [('AssertSomeThumbprint', 'build-thumbprint',
310 'source-build-thumbprint')],
311 script_writer.script)
312
313
314class LoadOemDictsTest(unittest.TestCase):
315
316 def tearDown(self):
317 common.Cleanup()
318
319 def test_NoneDict(self):
320 self.assertIsNone(_LoadOemDicts(None))
321
322 def test_SingleDict(self):
323 dict_file = common.MakeTempFile()
324 with open(dict_file, 'w') as dict_fp:
325 dict_fp.write('abc=1\ndef=2\nxyz=foo\na.b.c=bar\n')
326
327 oem_dicts = _LoadOemDicts([dict_file])
328 self.assertEqual(1, len(oem_dicts))
329 self.assertEqual('foo', oem_dicts[0]['xyz'])
330 self.assertEqual('bar', oem_dicts[0]['a.b.c'])
331
332 def test_MultipleDicts(self):
333 oem_source = []
334 for i in range(3):
335 dict_file = common.MakeTempFile()
336 with open(dict_file, 'w') as dict_fp:
337 dict_fp.write(
338 'ro.build.index={}\ndef=2\nxyz=foo\na.b.c=bar\n'.format(i))
339 oem_source.append(dict_file)
340
341 oem_dicts = _LoadOemDicts(oem_source)
342 self.assertEqual(3, len(oem_dicts))
343 for i, oem_dict in enumerate(oem_dicts):
344 self.assertEqual('2', oem_dict['def'])
345 self.assertEqual('foo', oem_dict['xyz'])
346 self.assertEqual('bar', oem_dict['a.b.c'])
347 self.assertEqual('{}'.format(i), oem_dict['ro.build.index'])
Tao Baodf3a48b2018-01-10 16:30:43 -0800348
349
350class OtaFromTargetFilesTest(unittest.TestCase):
351
352 TEST_TARGET_INFO_DICT = {
353 'build.prop' : {
354 'ro.product.device' : 'product-device',
355 'ro.build.fingerprint' : 'build-fingerprint-target',
356 'ro.build.version.incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800357 'ro.build.version.sdk' : '27',
358 'ro.build.version.security_patch' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800359 'ro.build.date.utc' : '1500000000',
360 },
361 }
362
363 TEST_SOURCE_INFO_DICT = {
364 'build.prop' : {
365 'ro.product.device' : 'product-device',
366 'ro.build.fingerprint' : 'build-fingerprint-source',
367 'ro.build.version.incremental' : 'build-version-incremental-source',
Tao Bao35dc2552018-02-01 13:18:00 -0800368 'ro.build.version.sdk' : '25',
369 'ro.build.version.security_patch' : '2016-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800370 'ro.build.date.utc' : '1400000000',
371 },
372 }
373
374 def setUp(self):
375 # Reset the global options as in ota_from_target_files.py.
376 common.OPTIONS.incremental_source = None
377 common.OPTIONS.downgrade = False
378 common.OPTIONS.timestamp = False
379 common.OPTIONS.wipe_user_data = False
380
Tao Baof5110492018-03-02 09:47:43 -0800381 def tearDown(self):
382 common.Cleanup()
383
Tao Baodf3a48b2018-01-10 16:30:43 -0800384 def test_GetPackageMetadata_abOta_full(self):
385 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
386 target_info_dict['ab_update'] = 'true'
387 target_info = BuildInfo(target_info_dict, None)
388 metadata = GetPackageMetadata(target_info)
389 self.assertDictEqual(
390 {
391 'ota-type' : 'AB',
392 'ota-required-cache' : '0',
393 'post-build' : 'build-fingerprint-target',
394 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800395 'post-sdk-level' : '27',
396 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800397 'post-timestamp' : '1500000000',
398 'pre-device' : 'product-device',
399 },
400 metadata)
401
402 def test_GetPackageMetadata_abOta_incremental(self):
403 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
404 target_info_dict['ab_update'] = 'true'
405 target_info = BuildInfo(target_info_dict, None)
406 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
407 common.OPTIONS.incremental_source = ''
408 metadata = GetPackageMetadata(target_info, source_info)
409 self.assertDictEqual(
410 {
411 'ota-type' : 'AB',
412 'ota-required-cache' : '0',
413 'post-build' : 'build-fingerprint-target',
414 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800415 'post-sdk-level' : '27',
416 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800417 'post-timestamp' : '1500000000',
418 'pre-device' : 'product-device',
419 'pre-build' : 'build-fingerprint-source',
420 'pre-build-incremental' : 'build-version-incremental-source',
421 },
422 metadata)
423
424 def test_GetPackageMetadata_nonAbOta_full(self):
425 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
426 metadata = GetPackageMetadata(target_info)
427 self.assertDictEqual(
428 {
429 'ota-type' : 'BLOCK',
430 'post-build' : 'build-fingerprint-target',
431 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800432 'post-sdk-level' : '27',
433 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800434 'post-timestamp' : '1500000000',
435 'pre-device' : 'product-device',
436 },
437 metadata)
438
439 def test_GetPackageMetadata_nonAbOta_incremental(self):
440 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
441 source_info = BuildInfo(self.TEST_SOURCE_INFO_DICT, None)
442 common.OPTIONS.incremental_source = ''
443 metadata = GetPackageMetadata(target_info, source_info)
444 self.assertDictEqual(
445 {
446 'ota-type' : 'BLOCK',
447 'post-build' : 'build-fingerprint-target',
448 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800449 'post-sdk-level' : '27',
450 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800451 'post-timestamp' : '1500000000',
452 'pre-device' : 'product-device',
453 'pre-build' : 'build-fingerprint-source',
454 'pre-build-incremental' : 'build-version-incremental-source',
455 },
456 metadata)
457
458 def test_GetPackageMetadata_wipe(self):
459 target_info = BuildInfo(self.TEST_TARGET_INFO_DICT, None)
460 common.OPTIONS.wipe_user_data = True
461 metadata = GetPackageMetadata(target_info)
462 self.assertDictEqual(
463 {
464 'ota-type' : 'BLOCK',
465 'ota-wipe' : 'yes',
466 'post-build' : 'build-fingerprint-target',
467 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800468 'post-sdk-level' : '27',
469 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800470 'post-timestamp' : '1500000000',
471 'pre-device' : 'product-device',
472 },
473 metadata)
474
475 @staticmethod
476 def _test_GetPackageMetadata_swapBuildTimestamps(target_info, source_info):
477 (target_info['build.prop']['ro.build.date.utc'],
478 source_info['build.prop']['ro.build.date.utc']) = (
479 source_info['build.prop']['ro.build.date.utc'],
480 target_info['build.prop']['ro.build.date.utc'])
481
482 def test_GetPackageMetadata_unintentionalDowngradeDetected(self):
483 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
484 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
485 self._test_GetPackageMetadata_swapBuildTimestamps(
486 target_info_dict, source_info_dict)
487
488 target_info = BuildInfo(target_info_dict, None)
489 source_info = BuildInfo(source_info_dict, None)
490 common.OPTIONS.incremental_source = ''
491 self.assertRaises(RuntimeError, GetPackageMetadata, target_info,
492 source_info)
493
494 def test_GetPackageMetadata_downgrade(self):
495 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
496 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
497 self._test_GetPackageMetadata_swapBuildTimestamps(
498 target_info_dict, source_info_dict)
499
500 target_info = BuildInfo(target_info_dict, None)
501 source_info = BuildInfo(source_info_dict, None)
502 common.OPTIONS.incremental_source = ''
503 common.OPTIONS.downgrade = True
504 common.OPTIONS.wipe_user_data = True
505 metadata = GetPackageMetadata(target_info, source_info)
506 self.assertDictEqual(
507 {
508 'ota-downgrade' : 'yes',
509 'ota-type' : 'BLOCK',
510 'ota-wipe' : 'yes',
511 'post-build' : 'build-fingerprint-target',
512 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800513 'post-sdk-level' : '27',
514 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800515 'pre-device' : 'product-device',
516 'pre-build' : 'build-fingerprint-source',
517 'pre-build-incremental' : 'build-version-incremental-source',
518 },
519 metadata)
520
521 def test_GetPackageMetadata_overrideTimestamp(self):
522 target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
523 source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
524 self._test_GetPackageMetadata_swapBuildTimestamps(
525 target_info_dict, source_info_dict)
526
527 target_info = BuildInfo(target_info_dict, None)
528 source_info = BuildInfo(source_info_dict, None)
529 common.OPTIONS.incremental_source = ''
530 common.OPTIONS.timestamp = True
531 metadata = GetPackageMetadata(target_info, source_info)
532 self.assertDictEqual(
533 {
534 'ota-type' : 'BLOCK',
535 'post-build' : 'build-fingerprint-target',
536 'post-build-incremental' : 'build-version-incremental-target',
Tao Bao35dc2552018-02-01 13:18:00 -0800537 'post-sdk-level' : '27',
538 'post-security-patch-level' : '2017-12-01',
Tao Baodf3a48b2018-01-10 16:30:43 -0800539 'post-timestamp' : '1500000001',
540 'pre-device' : 'product-device',
541 'pre-build' : 'build-fingerprint-source',
542 'pre-build-incremental' : 'build-version-incremental-source',
543 },
544 metadata)
Tao Baofabe0832018-01-17 15:52:28 -0800545
Tao Baof7140c02018-01-30 17:09:24 -0800546 def test_GetTargetFilesZipForSecondaryImages(self):
547 input_file = construct_target_files(secondary=True)
548 target_file = GetTargetFilesZipForSecondaryImages(input_file)
549
550 with zipfile.ZipFile(target_file) as verify_zip:
551 namelist = verify_zip.namelist()
552
553 self.assertIn('META/ab_partitions.txt', namelist)
554 self.assertIn('IMAGES/boot.img', namelist)
555 self.assertIn('IMAGES/system.img', namelist)
556 self.assertIn('IMAGES/vendor.img', namelist)
Tao Bao15a146a2018-02-21 16:06:59 -0800557 self.assertIn(POSTINSTALL_CONFIG, namelist)
Tao Baof7140c02018-01-30 17:09:24 -0800558
559 self.assertNotIn('IMAGES/system_other.img', namelist)
560 self.assertNotIn('IMAGES/system.map', namelist)
561
Tao Bao15a146a2018-02-21 16:06:59 -0800562 def test_GetTargetFilesZipForSecondaryImages_skipPostinstall(self):
563 input_file = construct_target_files(secondary=True)
564 target_file = GetTargetFilesZipForSecondaryImages(
565 input_file, skip_postinstall=True)
566
567 with zipfile.ZipFile(target_file) as verify_zip:
568 namelist = verify_zip.namelist()
569
570 self.assertIn('META/ab_partitions.txt', namelist)
571 self.assertIn('IMAGES/boot.img', namelist)
572 self.assertIn('IMAGES/system.img', namelist)
573 self.assertIn('IMAGES/vendor.img', namelist)
574
575 self.assertNotIn('IMAGES/system_other.img', namelist)
576 self.assertNotIn('IMAGES/system.map', namelist)
577 self.assertNotIn(POSTINSTALL_CONFIG, namelist)
578
579 def test_GetTargetFilesZipWithoutPostinstallConfig(self):
580 input_file = construct_target_files()
581 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
582 with zipfile.ZipFile(target_file) as verify_zip:
583 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
584
585 def test_GetTargetFilesZipWithoutPostinstallConfig_missingEntry(self):
586 input_file = construct_target_files()
587 common.ZipDelete(input_file, POSTINSTALL_CONFIG)
588 target_file = GetTargetFilesZipWithoutPostinstallConfig(input_file)
589 with zipfile.ZipFile(target_file) as verify_zip:
590 self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
591
Tao Baoae5e4c32018-03-01 19:30:00 -0800592
593class StreamingPropertyFilesTest(unittest.TestCase):
594
595 def tearDown(self):
596 common.Cleanup()
597
Tao Baof5110492018-03-02 09:47:43 -0800598 @staticmethod
599 def _construct_zip_package(entries):
600 zip_file = common.MakeTempFile(suffix='.zip')
601 with zipfile.ZipFile(zip_file, 'w') as zip_fp:
602 for entry in entries:
603 zip_fp.writestr(
604 entry,
605 entry.replace('.', '-').upper(),
606 zipfile.ZIP_STORED)
607 return zip_file
608
609 @staticmethod
610 def _parse_streaming_metadata_string(data):
611 result = {}
612 for token in data.split(','):
613 name, info = token.split(':', 1)
614 result[name] = info
615 return result
616
617 def _verify_entries(self, input_file, tokens, entries):
618 for entry in entries:
619 offset, size = map(int, tokens[entry].split(':'))
620 with open(input_file, 'rb') as input_fp:
621 input_fp.seek(offset)
622 if entry == 'metadata':
623 expected = b'META-INF/COM/ANDROID/METADATA'
624 else:
625 expected = entry.replace('.', '-').upper().encode()
626 self.assertEqual(expected, input_fp.read(size))
627
Tao Baoae5e4c32018-03-01 19:30:00 -0800628 def test_Compute(self):
Tao Baof5110492018-03-02 09:47:43 -0800629 entries = (
630 'payload.bin',
631 'payload_properties.txt',
632 )
633 zip_file = self._construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800634 property_files = StreamingPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800635 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Baoae5e4c32018-03-01 19:30:00 -0800636 streaming_metadata = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800637
Tao Baoae5e4c32018-03-01 19:30:00 -0800638 tokens = self._parse_streaming_metadata_string(streaming_metadata)
Tao Baof5110492018-03-02 09:47:43 -0800639 self.assertEqual(3, len(tokens))
640 self._verify_entries(zip_file, tokens, entries)
641
Tao Baoae5e4c32018-03-01 19:30:00 -0800642 def test_Compute_withCareMapTxtAndCompatibilityZip(self):
Tao Baof5110492018-03-02 09:47:43 -0800643 entries = (
644 'payload.bin',
645 'payload_properties.txt',
646 'care_map.txt',
647 'compatibility.zip',
648 )
649 zip_file = self._construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800650 property_files = StreamingPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800651 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Baoae5e4c32018-03-01 19:30:00 -0800652 streaming_metadata = property_files.Compute(zip_fp)
Tao Baof5110492018-03-02 09:47:43 -0800653
Tao Baoae5e4c32018-03-01 19:30:00 -0800654 tokens = self._parse_streaming_metadata_string(streaming_metadata)
Tao Baof5110492018-03-02 09:47:43 -0800655 self.assertEqual(5, len(tokens))
656 self._verify_entries(zip_file, tokens, entries)
657
Tao Baoae5e4c32018-03-01 19:30:00 -0800658 def test_Finalize(self):
Tao Baof5110492018-03-02 09:47:43 -0800659 entries = [
660 'payload.bin',
661 'payload_properties.txt',
662 'META-INF/com/android/metadata',
663 ]
664 zip_file = self._construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800665 property_files = StreamingPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800666 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
Tao Baoae5e4c32018-03-01 19:30:00 -0800667 raw_metadata = property_files._GetPropertyFilesString(
668 zip_fp, reserve_space=False)
669 streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
Tao Baof5110492018-03-02 09:47:43 -0800670 tokens = self._parse_streaming_metadata_string(streaming_metadata)
671
672 self.assertEqual(3, len(tokens))
673 # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
674 # streaming metadata.
675 entries[2] = 'metadata'
676 self._verify_entries(zip_file, tokens, entries)
677
Tao Baoae5e4c32018-03-01 19:30:00 -0800678 def test_Finalize_assertReservedLength(self):
Tao Baof5110492018-03-02 09:47:43 -0800679 entries = (
680 'payload.bin',
681 'payload_properties.txt',
682 'care_map.txt',
683 'META-INF/com/android/metadata',
684 )
685 zip_file = self._construct_zip_package(entries)
Tao Baoae5e4c32018-03-01 19:30:00 -0800686 property_files = StreamingPropertyFiles()
Tao Baof5110492018-03-02 09:47:43 -0800687 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
688 # First get the raw metadata string (i.e. without padding space).
Tao Baoae5e4c32018-03-01 19:30:00 -0800689 raw_metadata = property_files._GetPropertyFilesString(
690 zip_fp, reserve_space=False)
Tao Baof5110492018-03-02 09:47:43 -0800691 raw_length = len(raw_metadata)
692
693 # Now pass in the exact expected length.
Tao Baoae5e4c32018-03-01 19:30:00 -0800694 streaming_metadata = property_files.Finalize(zip_fp, raw_length)
Tao Baof5110492018-03-02 09:47:43 -0800695 self.assertEqual(raw_length, len(streaming_metadata))
696
697 # Or pass in insufficient length.
698 self.assertRaises(
699 AssertionError,
Tao Baoae5e4c32018-03-01 19:30:00 -0800700 property_files.Finalize,
Tao Baof5110492018-03-02 09:47:43 -0800701 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800702 raw_length - 1)
Tao Baof5110492018-03-02 09:47:43 -0800703
704 # Or pass in a much larger size.
Tao Baoae5e4c32018-03-01 19:30:00 -0800705 streaming_metadata = property_files.Finalize(
Tao Baof5110492018-03-02 09:47:43 -0800706 zip_fp,
Tao Baoae5e4c32018-03-01 19:30:00 -0800707 raw_length + 20)
Tao Baof5110492018-03-02 09:47:43 -0800708 self.assertEqual(raw_length + 20, len(streaming_metadata))
709 self.assertEqual(' ' * 20, streaming_metadata[raw_length:])
710
Tao Baoae5e4c32018-03-01 19:30:00 -0800711 def test_Verify(self):
712 entries = (
713 'payload.bin',
714 'payload_properties.txt',
715 'care_map.txt',
716 'META-INF/com/android/metadata',
717 )
718 zip_file = self._construct_zip_package(entries)
719 property_files = StreamingPropertyFiles()
720 with zipfile.ZipFile(zip_file, 'r') as zip_fp:
721 # First get the raw metadata string (i.e. without padding space).
722 raw_metadata = property_files._GetPropertyFilesString(
723 zip_fp, reserve_space=False)
724
725 # Should pass the test if verification passes.
726 property_files.Verify(zip_fp, raw_metadata)
727
728 # Or raise on verification failure.
729 self.assertRaises(
730 AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
731
Tao Baofabe0832018-01-17 15:52:28 -0800732
733class PayloadSignerTest(unittest.TestCase):
734
735 SIGFILE = 'sigfile.bin'
736 SIGNED_SIGFILE = 'signed-sigfile.bin'
737
738 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -0800739 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baofabe0832018-01-17 15:52:28 -0800740 self.assertTrue(os.path.exists(self.testdata_dir))
741
742 common.OPTIONS.payload_signer = None
743 common.OPTIONS.payload_signer_args = []
744 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
745 common.OPTIONS.key_passwords = {
746 common.OPTIONS.package_key : None,
747 }
748
749 def tearDown(self):
750 common.Cleanup()
751
752 def _assertFilesEqual(self, file1, file2):
753 with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2:
754 self.assertEqual(fp1.read(), fp2.read())
755
756 def test_init(self):
757 payload_signer = PayloadSigner()
758 self.assertEqual('openssl', payload_signer.signer)
759
760 def test_init_withPassword(self):
761 common.OPTIONS.package_key = os.path.join(
762 self.testdata_dir, 'testkey_with_passwd')
763 common.OPTIONS.key_passwords = {
764 common.OPTIONS.package_key : 'foo',
765 }
766 payload_signer = PayloadSigner()
767 self.assertEqual('openssl', payload_signer.signer)
768
769 def test_init_withExternalSigner(self):
770 common.OPTIONS.payload_signer = 'abc'
771 common.OPTIONS.payload_signer_args = ['arg1', 'arg2']
772 payload_signer = PayloadSigner()
773 self.assertEqual('abc', payload_signer.signer)
774 self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args)
775
776 def test_Sign(self):
777 payload_signer = PayloadSigner()
778 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
779 signed_file = payload_signer.Sign(input_file)
780
781 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
782 self._assertFilesEqual(verify_file, signed_file)
783
784 def test_Sign_withExternalSigner_openssl(self):
785 """Uses openssl as the external payload signer."""
786 common.OPTIONS.payload_signer = 'openssl'
787 common.OPTIONS.payload_signer_args = [
788 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey',
789 os.path.join(self.testdata_dir, 'testkey.pk8'),
790 '-pkeyopt', 'digest:sha256']
791 payload_signer = PayloadSigner()
792 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
793 signed_file = payload_signer.Sign(input_file)
794
795 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
796 self._assertFilesEqual(verify_file, signed_file)
797
798 def test_Sign_withExternalSigner_script(self):
799 """Uses testdata/payload_signer.sh as the external payload signer."""
800 common.OPTIONS.payload_signer = os.path.join(
801 self.testdata_dir, 'payload_signer.sh')
802 common.OPTIONS.payload_signer_args = [
803 os.path.join(self.testdata_dir, 'testkey.pk8')]
804 payload_signer = PayloadSigner()
805 input_file = os.path.join(self.testdata_dir, self.SIGFILE)
806 signed_file = payload_signer.Sign(input_file)
807
808 verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
809 self._assertFilesEqual(verify_file, signed_file)
Tao Baoc7b403a2018-01-30 18:19:04 -0800810
811
812class PayloadTest(unittest.TestCase):
813
814 def setUp(self):
Tao Bao04e1f012018-02-04 12:13:35 -0800815 self.testdata_dir = test_utils.get_testdata_dir()
Tao Baoc7b403a2018-01-30 18:19:04 -0800816 self.assertTrue(os.path.exists(self.testdata_dir))
817
818 common.OPTIONS.wipe_user_data = False
819 common.OPTIONS.payload_signer = None
820 common.OPTIONS.payload_signer_args = None
821 common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
822 common.OPTIONS.key_passwords = {
823 common.OPTIONS.package_key : None,
824 }
825
826 def tearDown(self):
827 common.Cleanup()
828
829 @staticmethod
Tao Baof7140c02018-01-30 17:09:24 -0800830 def _create_payload_full(secondary=False):
831 target_file = construct_target_files(secondary)
Tao Bao667ff572018-02-10 00:02:40 -0800832 payload = Payload(secondary)
Tao Baoc7b403a2018-01-30 18:19:04 -0800833 payload.Generate(target_file)
834 return payload
835
Tao Baof7140c02018-01-30 17:09:24 -0800836 @staticmethod
837 def _create_payload_incremental():
838 target_file = construct_target_files()
839 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -0800840 payload = Payload()
841 payload.Generate(target_file, source_file)
842 return payload
843
844 def test_Generate_full(self):
845 payload = self._create_payload_full()
846 self.assertTrue(os.path.exists(payload.payload_file))
847
848 def test_Generate_incremental(self):
849 payload = self._create_payload_incremental()
850 self.assertTrue(os.path.exists(payload.payload_file))
851
852 def test_Generate_additionalArgs(self):
Tao Baof7140c02018-01-30 17:09:24 -0800853 target_file = construct_target_files()
854 source_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -0800855 payload = Payload()
856 # This should work the same as calling payload.Generate(target_file,
857 # source_file).
858 payload.Generate(
859 target_file, additional_args=["--source_image", source_file])
860 self.assertTrue(os.path.exists(payload.payload_file))
861
862 def test_Generate_invalidInput(self):
Tao Baof7140c02018-01-30 17:09:24 -0800863 target_file = construct_target_files()
Tao Baoc7b403a2018-01-30 18:19:04 -0800864 common.ZipDelete(target_file, 'IMAGES/vendor.img')
865 payload = Payload()
866 self.assertRaises(AssertionError, payload.Generate, target_file)
867
868 def test_Sign_full(self):
869 payload = self._create_payload_full()
870 payload.Sign(PayloadSigner())
871
872 output_file = common.MakeTempFile(suffix='.zip')
873 with zipfile.ZipFile(output_file, 'w') as output_zip:
874 payload.WriteToZip(output_zip)
875
876 import check_ota_package_signature
877 check_ota_package_signature.VerifyAbOtaPayload(
878 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
879 output_file)
880
881 def test_Sign_incremental(self):
882 payload = self._create_payload_incremental()
883 payload.Sign(PayloadSigner())
884
885 output_file = common.MakeTempFile(suffix='.zip')
886 with zipfile.ZipFile(output_file, 'w') as output_zip:
887 payload.WriteToZip(output_zip)
888
889 import check_ota_package_signature
890 check_ota_package_signature.VerifyAbOtaPayload(
891 os.path.join(self.testdata_dir, 'testkey.x509.pem'),
892 output_file)
893
894 def test_Sign_withDataWipe(self):
895 common.OPTIONS.wipe_user_data = True
896 payload = self._create_payload_full()
897 payload.Sign(PayloadSigner())
898
899 with open(payload.payload_properties) as properties_fp:
900 self.assertIn("POWERWASH=1", properties_fp.read())
901
Tao Bao667ff572018-02-10 00:02:40 -0800902 def test_Sign_secondary(self):
903 payload = self._create_payload_full(secondary=True)
904 payload.Sign(PayloadSigner())
905
906 with open(payload.payload_properties) as properties_fp:
907 self.assertIn("SWITCH_SLOT_ON_REBOOT=0", properties_fp.read())
908
Tao Baoc7b403a2018-01-30 18:19:04 -0800909 def test_Sign_badSigner(self):
910 """Tests that signing failure can be captured."""
911 payload = self._create_payload_full()
912 payload_signer = PayloadSigner()
913 payload_signer.signer_args.append('bad-option')
914 self.assertRaises(AssertionError, payload.Sign, payload_signer)
915
916 def test_WriteToZip(self):
917 payload = self._create_payload_full()
918 payload.Sign(PayloadSigner())
919
920 output_file = common.MakeTempFile(suffix='.zip')
921 with zipfile.ZipFile(output_file, 'w') as output_zip:
922 payload.WriteToZip(output_zip)
923
924 with zipfile.ZipFile(output_file) as verify_zip:
925 # First make sure we have the essential entries.
926 namelist = verify_zip.namelist()
927 self.assertIn(Payload.PAYLOAD_BIN, namelist)
928 self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist)
929
930 # Then assert these entries are stored.
931 for entry_info in verify_zip.infolist():
932 if entry_info.filename not in (Payload.PAYLOAD_BIN,
933 Payload.PAYLOAD_PROPERTIES_TXT):
934 continue
935 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
936
937 def test_WriteToZip_unsignedPayload(self):
938 """Unsigned payloads should not be allowed to be written to zip."""
939 payload = self._create_payload_full()
940
941 output_file = common.MakeTempFile(suffix='.zip')
942 with zipfile.ZipFile(output_file, 'w') as output_zip:
943 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
944
945 # Also test with incremental payload.
946 payload = self._create_payload_incremental()
947
948 output_file = common.MakeTempFile(suffix='.zip')
949 with zipfile.ZipFile(output_file, 'w') as output_zip:
950 self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
Tao Baof7140c02018-01-30 17:09:24 -0800951
952 def test_WriteToZip_secondary(self):
953 payload = self._create_payload_full(secondary=True)
954 payload.Sign(PayloadSigner())
955
956 output_file = common.MakeTempFile(suffix='.zip')
957 with zipfile.ZipFile(output_file, 'w') as output_zip:
Tao Bao667ff572018-02-10 00:02:40 -0800958 payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -0800959
960 with zipfile.ZipFile(output_file) as verify_zip:
961 # First make sure we have the essential entries.
962 namelist = verify_zip.namelist()
963 self.assertIn(Payload.SECONDARY_PAYLOAD_BIN, namelist)
964 self.assertIn(Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT, namelist)
965
966 # Then assert these entries are stored.
967 for entry_info in verify_zip.infolist():
968 if entry_info.filename not in (
969 Payload.SECONDARY_PAYLOAD_BIN,
970 Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT):
971 continue
972 self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)