blob: cc59cba11b9c4afba74531b0b04a455d65235392 [file] [log] [blame]
Dan Albert8e0178d2015-01-27 15:53:15 -08001#
2# Copyright (C) 2015 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#
Tao Baofc7e0e02018-02-13 13:54:02 -080016
Tao Baoa57ab9f2018-08-24 12:08:38 -070017import copy
Dan Albert8e0178d2015-01-27 15:53:15 -080018import os
Tao Bao17e4e612018-02-16 17:12:54 -080019import subprocess
Dan Albert8e0178d2015-01-27 15:53:15 -080020import tempfile
21import time
Dan Albert8e0178d2015-01-27 15:53:15 -080022import zipfile
Tao Bao31b08072017-11-08 15:50:59 -080023from hashlib import sha1
24
Dan Albert8e0178d2015-01-27 15:53:15 -080025import common
Tao Bao04e1f012018-02-04 12:13:35 -080026import test_utils
Tianjie Xu9c384d22017-06-20 17:00:55 -070027import validate_target_files
Tianjie Xu41976c72019-07-03 13:57:01 -070028from images import EmptyImage, DataImage
Tao Baofc7e0e02018-02-13 13:54:02 -080029from rangelib import RangeSet
Dan Albert8e0178d2015-01-27 15:53:15 -080030
Tao Bao04e1f012018-02-04 12:13:35 -080031
Tao Bao31b08072017-11-08 15:50:59 -080032KiB = 1024
33MiB = 1024 * KiB
34GiB = 1024 * MiB
Dan Albert8e0178d2015-01-27 15:53:15 -080035
Tao Bao1c830bf2017-12-25 10:43:47 -080036
Tao Baof3282b42015-04-01 11:21:55 -070037def get_2gb_string():
Tao Bao31b08072017-11-08 15:50:59 -080038 size = int(2 * GiB + 1)
39 block_size = 4 * KiB
40 step_size = 4 * MiB
41 # Generate a long string with holes, e.g. 'xyz\x00abc\x00...'.
42 for _ in range(0, size, step_size):
43 yield os.urandom(block_size)
Tao Baoc1a1ec32019-06-18 16:29:37 -070044 yield b'\0' * (step_size - block_size)
Tao Baof3282b42015-04-01 11:21:55 -070045
Dan Albert8e0178d2015-01-27 15:53:15 -080046
Tao Bao65b94e92018-10-11 21:57:26 -070047class CommonZipTest(test_utils.ReleaseToolsTestCase):
48
Tao Bao31b08072017-11-08 15:50:59 -080049 def _verify(self, zip_file, zip_file_name, arcname, expected_hash,
Tao Baof3282b42015-04-01 11:21:55 -070050 test_file_name=None, expected_stat=None, expected_mode=0o644,
51 expected_compress_type=zipfile.ZIP_STORED):
52 # Verify the stat if present.
53 if test_file_name is not None:
54 new_stat = os.stat(test_file_name)
55 self.assertEqual(int(expected_stat.st_mode), int(new_stat.st_mode))
56 self.assertEqual(int(expected_stat.st_mtime), int(new_stat.st_mtime))
57
58 # Reopen the zip file to verify.
59 zip_file = zipfile.ZipFile(zip_file_name, "r")
60
61 # Verify the timestamp.
62 info = zip_file.getinfo(arcname)
63 self.assertEqual(info.date_time, (2009, 1, 1, 0, 0, 0))
64
65 # Verify the file mode.
66 mode = (info.external_attr >> 16) & 0o777
67 self.assertEqual(mode, expected_mode)
68
69 # Verify the compress type.
70 self.assertEqual(info.compress_type, expected_compress_type)
71
72 # Verify the zip contents.
Tao Bao31b08072017-11-08 15:50:59 -080073 entry = zip_file.open(arcname)
74 sha1_hash = sha1()
Tao Baoc1a1ec32019-06-18 16:29:37 -070075 for chunk in iter(lambda: entry.read(4 * MiB), b''):
Tao Bao31b08072017-11-08 15:50:59 -080076 sha1_hash.update(chunk)
77 self.assertEqual(expected_hash, sha1_hash.hexdigest())
Tao Baof3282b42015-04-01 11:21:55 -070078 self.assertIsNone(zip_file.testzip())
79
Dan Albert8e0178d2015-01-27 15:53:15 -080080 def _test_ZipWrite(self, contents, extra_zipwrite_args=None):
81 extra_zipwrite_args = dict(extra_zipwrite_args or {})
82
83 test_file = tempfile.NamedTemporaryFile(delete=False)
Dan Albert8e0178d2015-01-27 15:53:15 -080084 test_file_name = test_file.name
Tao Baof3282b42015-04-01 11:21:55 -070085
86 zip_file = tempfile.NamedTemporaryFile(delete=False)
Dan Albert8e0178d2015-01-27 15:53:15 -080087 zip_file_name = zip_file.name
88
89 # File names within an archive strip the leading slash.
90 arcname = extra_zipwrite_args.get("arcname", test_file_name)
91 if arcname[0] == "/":
92 arcname = arcname[1:]
93
94 zip_file.close()
95 zip_file = zipfile.ZipFile(zip_file_name, "w")
96
97 try:
Tao Bao31b08072017-11-08 15:50:59 -080098 sha1_hash = sha1()
99 for data in contents:
Tao Baoc1a1ec32019-06-18 16:29:37 -0700100 sha1_hash.update(bytes(data))
101 test_file.write(bytes(data))
Dan Albert8e0178d2015-01-27 15:53:15 -0800102 test_file.close()
103
Tao Baof3282b42015-04-01 11:21:55 -0700104 expected_stat = os.stat(test_file_name)
Dan Albert8e0178d2015-01-27 15:53:15 -0800105 expected_mode = extra_zipwrite_args.get("perms", 0o644)
Tao Baof3282b42015-04-01 11:21:55 -0700106 expected_compress_type = extra_zipwrite_args.get("compress_type",
107 zipfile.ZIP_STORED)
Dan Albert8e0178d2015-01-27 15:53:15 -0800108 time.sleep(5) # Make sure the atime/mtime will change measurably.
109
110 common.ZipWrite(zip_file, test_file_name, **extra_zipwrite_args)
Tao Baof3282b42015-04-01 11:21:55 -0700111 common.ZipClose(zip_file)
Dan Albert8e0178d2015-01-27 15:53:15 -0800112
Tao Bao31b08072017-11-08 15:50:59 -0800113 self._verify(zip_file, zip_file_name, arcname, sha1_hash.hexdigest(),
114 test_file_name, expected_stat, expected_mode,
115 expected_compress_type)
Dan Albert8e0178d2015-01-27 15:53:15 -0800116 finally:
117 os.remove(test_file_name)
118 os.remove(zip_file_name)
119
Tao Baof3282b42015-04-01 11:21:55 -0700120 def _test_ZipWriteStr(self, zinfo_or_arcname, contents, extra_args=None):
121 extra_args = dict(extra_args or {})
122
123 zip_file = tempfile.NamedTemporaryFile(delete=False)
124 zip_file_name = zip_file.name
125 zip_file.close()
126
127 zip_file = zipfile.ZipFile(zip_file_name, "w")
128
129 try:
130 expected_compress_type = extra_args.get("compress_type",
131 zipfile.ZIP_STORED)
132 time.sleep(5) # Make sure the atime/mtime will change measurably.
133
134 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
Tao Bao58c1b962015-05-20 09:32:18 -0700135 arcname = zinfo_or_arcname
136 expected_mode = extra_args.get("perms", 0o644)
Tao Baof3282b42015-04-01 11:21:55 -0700137 else:
Tao Bao58c1b962015-05-20 09:32:18 -0700138 arcname = zinfo_or_arcname.filename
Tao Baoc1a1ec32019-06-18 16:29:37 -0700139 if zinfo_or_arcname.external_attr:
140 zinfo_perms = zinfo_or_arcname.external_attr >> 16
141 else:
142 zinfo_perms = 0o600
143 expected_mode = extra_args.get("perms", zinfo_perms)
Tao Baof3282b42015-04-01 11:21:55 -0700144
Tao Bao58c1b962015-05-20 09:32:18 -0700145 common.ZipWriteStr(zip_file, zinfo_or_arcname, contents, **extra_args)
Tao Baof3282b42015-04-01 11:21:55 -0700146 common.ZipClose(zip_file)
147
Tao Bao31b08072017-11-08 15:50:59 -0800148 self._verify(zip_file, zip_file_name, arcname, sha1(contents).hexdigest(),
Tao Bao58c1b962015-05-20 09:32:18 -0700149 expected_mode=expected_mode,
Tao Baof3282b42015-04-01 11:21:55 -0700150 expected_compress_type=expected_compress_type)
151 finally:
152 os.remove(zip_file_name)
153
154 def _test_ZipWriteStr_large_file(self, large, small, extra_args=None):
155 extra_args = dict(extra_args or {})
156
157 zip_file = tempfile.NamedTemporaryFile(delete=False)
158 zip_file_name = zip_file.name
159
160 test_file = tempfile.NamedTemporaryFile(delete=False)
161 test_file_name = test_file.name
162
163 arcname_large = test_file_name
164 arcname_small = "bar"
165
166 # File names within an archive strip the leading slash.
167 if arcname_large[0] == "/":
168 arcname_large = arcname_large[1:]
169
170 zip_file.close()
171 zip_file = zipfile.ZipFile(zip_file_name, "w")
172
173 try:
Tao Bao31b08072017-11-08 15:50:59 -0800174 sha1_hash = sha1()
175 for data in large:
176 sha1_hash.update(data)
177 test_file.write(data)
Tao Baof3282b42015-04-01 11:21:55 -0700178 test_file.close()
179
180 expected_stat = os.stat(test_file_name)
181 expected_mode = 0o644
182 expected_compress_type = extra_args.get("compress_type",
183 zipfile.ZIP_STORED)
184 time.sleep(5) # Make sure the atime/mtime will change measurably.
185
186 common.ZipWrite(zip_file, test_file_name, **extra_args)
187 common.ZipWriteStr(zip_file, arcname_small, small, **extra_args)
188 common.ZipClose(zip_file)
189
190 # Verify the contents written by ZipWrite().
Tao Bao31b08072017-11-08 15:50:59 -0800191 self._verify(zip_file, zip_file_name, arcname_large,
192 sha1_hash.hexdigest(), test_file_name, expected_stat,
193 expected_mode, expected_compress_type)
Tao Baof3282b42015-04-01 11:21:55 -0700194
195 # Verify the contents written by ZipWriteStr().
Tao Bao31b08072017-11-08 15:50:59 -0800196 self._verify(zip_file, zip_file_name, arcname_small,
197 sha1(small).hexdigest(),
Tao Baof3282b42015-04-01 11:21:55 -0700198 expected_compress_type=expected_compress_type)
199 finally:
200 os.remove(zip_file_name)
201 os.remove(test_file_name)
202
203 def _test_reset_ZIP64_LIMIT(self, func, *args):
204 default_limit = (1 << 31) - 1
205 self.assertEqual(default_limit, zipfile.ZIP64_LIMIT)
206 func(*args)
207 self.assertEqual(default_limit, zipfile.ZIP64_LIMIT)
208
Dan Albert8e0178d2015-01-27 15:53:15 -0800209 def test_ZipWrite(self):
210 file_contents = os.urandom(1024)
211 self._test_ZipWrite(file_contents)
212
213 def test_ZipWrite_with_opts(self):
214 file_contents = os.urandom(1024)
215 self._test_ZipWrite(file_contents, {
216 "arcname": "foobar",
217 "perms": 0o777,
218 "compress_type": zipfile.ZIP_DEFLATED,
219 })
Tao Baof3282b42015-04-01 11:21:55 -0700220 self._test_ZipWrite(file_contents, {
221 "arcname": "foobar",
222 "perms": 0o700,
223 "compress_type": zipfile.ZIP_STORED,
224 })
Dan Albert8e0178d2015-01-27 15:53:15 -0800225
226 def test_ZipWrite_large_file(self):
Tao Baof3282b42015-04-01 11:21:55 -0700227 file_contents = get_2gb_string()
Dan Albert8e0178d2015-01-27 15:53:15 -0800228 self._test_ZipWrite(file_contents, {
229 "compress_type": zipfile.ZIP_DEFLATED,
230 })
231
232 def test_ZipWrite_resets_ZIP64_LIMIT(self):
Tao Baof3282b42015-04-01 11:21:55 -0700233 self._test_reset_ZIP64_LIMIT(self._test_ZipWrite, "")
234
235 def test_ZipWriteStr(self):
236 random_string = os.urandom(1024)
237 # Passing arcname
238 self._test_ZipWriteStr("foo", random_string)
239
240 # Passing zinfo
241 zinfo = zipfile.ZipInfo(filename="foo")
242 self._test_ZipWriteStr(zinfo, random_string)
243
244 # Timestamp in the zinfo should be overwritten.
245 zinfo.date_time = (2015, 3, 1, 15, 30, 0)
246 self._test_ZipWriteStr(zinfo, random_string)
247
248 def test_ZipWriteStr_with_opts(self):
249 random_string = os.urandom(1024)
250 # Passing arcname
251 self._test_ZipWriteStr("foo", random_string, {
Tao Bao58c1b962015-05-20 09:32:18 -0700252 "perms": 0o700,
Tao Baof3282b42015-04-01 11:21:55 -0700253 "compress_type": zipfile.ZIP_DEFLATED,
254 })
Tao Bao58c1b962015-05-20 09:32:18 -0700255 self._test_ZipWriteStr("bar", random_string, {
Tao Baof3282b42015-04-01 11:21:55 -0700256 "compress_type": zipfile.ZIP_STORED,
257 })
258
259 # Passing zinfo
260 zinfo = zipfile.ZipInfo(filename="foo")
261 self._test_ZipWriteStr(zinfo, random_string, {
262 "compress_type": zipfile.ZIP_DEFLATED,
263 })
264 self._test_ZipWriteStr(zinfo, random_string, {
Tao Bao58c1b962015-05-20 09:32:18 -0700265 "perms": 0o600,
Tao Baof3282b42015-04-01 11:21:55 -0700266 "compress_type": zipfile.ZIP_STORED,
267 })
Tao Baoc1a1ec32019-06-18 16:29:37 -0700268 self._test_ZipWriteStr(zinfo, random_string, {
269 "perms": 0o000,
270 "compress_type": zipfile.ZIP_STORED,
271 })
Tao Baof3282b42015-04-01 11:21:55 -0700272
273 def test_ZipWriteStr_large_file(self):
274 # zipfile.writestr() doesn't work when the str size is over 2GiB even with
275 # the workaround. We will only test the case of writing a string into a
276 # large archive.
277 long_string = get_2gb_string()
278 short_string = os.urandom(1024)
279 self._test_ZipWriteStr_large_file(long_string, short_string, {
280 "compress_type": zipfile.ZIP_DEFLATED,
281 })
282
283 def test_ZipWriteStr_resets_ZIP64_LIMIT(self):
Tao Baoc1a1ec32019-06-18 16:29:37 -0700284 self._test_reset_ZIP64_LIMIT(self._test_ZipWriteStr, 'foo', b'')
Tao Baof3282b42015-04-01 11:21:55 -0700285 zinfo = zipfile.ZipInfo(filename="foo")
Tao Baoc1a1ec32019-06-18 16:29:37 -0700286 self._test_reset_ZIP64_LIMIT(self._test_ZipWriteStr, zinfo, b'')
Tao Bao58c1b962015-05-20 09:32:18 -0700287
288 def test_bug21309935(self):
289 zip_file = tempfile.NamedTemporaryFile(delete=False)
290 zip_file_name = zip_file.name
291 zip_file.close()
292
293 try:
294 random_string = os.urandom(1024)
295 zip_file = zipfile.ZipFile(zip_file_name, "w")
296 # Default perms should be 0o644 when passing the filename.
297 common.ZipWriteStr(zip_file, "foo", random_string)
298 # Honor the specified perms.
299 common.ZipWriteStr(zip_file, "bar", random_string, perms=0o755)
300 # The perms in zinfo should be untouched.
301 zinfo = zipfile.ZipInfo(filename="baz")
302 zinfo.external_attr = 0o740 << 16
303 common.ZipWriteStr(zip_file, zinfo, random_string)
304 # Explicitly specified perms has the priority.
305 zinfo = zipfile.ZipInfo(filename="qux")
306 zinfo.external_attr = 0o700 << 16
307 common.ZipWriteStr(zip_file, zinfo, random_string, perms=0o400)
308 common.ZipClose(zip_file)
309
Tao Bao31b08072017-11-08 15:50:59 -0800310 self._verify(zip_file, zip_file_name, "foo",
311 sha1(random_string).hexdigest(),
Tao Bao58c1b962015-05-20 09:32:18 -0700312 expected_mode=0o644)
Tao Bao31b08072017-11-08 15:50:59 -0800313 self._verify(zip_file, zip_file_name, "bar",
314 sha1(random_string).hexdigest(),
Tao Bao58c1b962015-05-20 09:32:18 -0700315 expected_mode=0o755)
Tao Bao31b08072017-11-08 15:50:59 -0800316 self._verify(zip_file, zip_file_name, "baz",
317 sha1(random_string).hexdigest(),
Tao Bao58c1b962015-05-20 09:32:18 -0700318 expected_mode=0o740)
Tao Bao31b08072017-11-08 15:50:59 -0800319 self._verify(zip_file, zip_file_name, "qux",
320 sha1(random_string).hexdigest(),
Tao Bao58c1b962015-05-20 09:32:18 -0700321 expected_mode=0o400)
322 finally:
323 os.remove(zip_file_name)
Tianjie Xu9c384d22017-06-20 17:00:55 -0700324
Tao Bao82490d32019-04-09 00:12:30 -0700325 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao89d7ab22017-12-14 17:05:33 -0800326 def test_ZipDelete(self):
327 zip_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
328 output_zip = zipfile.ZipFile(zip_file.name, 'w',
329 compression=zipfile.ZIP_DEFLATED)
330 with tempfile.NamedTemporaryFile() as entry_file:
331 entry_file.write(os.urandom(1024))
332 common.ZipWrite(output_zip, entry_file.name, arcname='Test1')
333 common.ZipWrite(output_zip, entry_file.name, arcname='Test2')
334 common.ZipWrite(output_zip, entry_file.name, arcname='Test3')
335 common.ZipClose(output_zip)
336 zip_file.close()
337
338 try:
339 common.ZipDelete(zip_file.name, 'Test2')
340 with zipfile.ZipFile(zip_file.name, 'r') as check_zip:
341 entries = check_zip.namelist()
342 self.assertTrue('Test1' in entries)
343 self.assertFalse('Test2' in entries)
344 self.assertTrue('Test3' in entries)
345
Tao Bao986ee862018-10-04 15:46:16 -0700346 self.assertRaises(
347 common.ExternalError, common.ZipDelete, zip_file.name, 'Test2')
Tao Bao89d7ab22017-12-14 17:05:33 -0800348 with zipfile.ZipFile(zip_file.name, 'r') as check_zip:
349 entries = check_zip.namelist()
350 self.assertTrue('Test1' in entries)
351 self.assertFalse('Test2' in entries)
352 self.assertTrue('Test3' in entries)
353
354 common.ZipDelete(zip_file.name, ['Test3'])
355 with zipfile.ZipFile(zip_file.name, 'r') as check_zip:
356 entries = check_zip.namelist()
357 self.assertTrue('Test1' in entries)
358 self.assertFalse('Test2' in entries)
359 self.assertFalse('Test3' in entries)
360
361 common.ZipDelete(zip_file.name, ['Test1', 'Test2'])
362 with zipfile.ZipFile(zip_file.name, 'r') as check_zip:
363 entries = check_zip.namelist()
364 self.assertFalse('Test1' in entries)
365 self.assertFalse('Test2' in entries)
366 self.assertFalse('Test3' in entries)
367 finally:
368 os.remove(zip_file.name)
369
Tao Bao0ff15de2019-03-20 11:26:06 -0700370 @staticmethod
371 def _test_UnzipTemp_createZipFile():
372 zip_file = common.MakeTempFile(suffix='.zip')
373 output_zip = zipfile.ZipFile(
374 zip_file, 'w', compression=zipfile.ZIP_DEFLATED)
375 contents = os.urandom(1024)
376 with tempfile.NamedTemporaryFile() as entry_file:
377 entry_file.write(contents)
378 common.ZipWrite(output_zip, entry_file.name, arcname='Test1')
379 common.ZipWrite(output_zip, entry_file.name, arcname='Test2')
380 common.ZipWrite(output_zip, entry_file.name, arcname='Foo3')
381 common.ZipWrite(output_zip, entry_file.name, arcname='Bar4')
382 common.ZipWrite(output_zip, entry_file.name, arcname='Dir5/Baz5')
383 common.ZipClose(output_zip)
384 common.ZipClose(output_zip)
385 return zip_file
386
Tao Bao82490d32019-04-09 00:12:30 -0700387 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao0ff15de2019-03-20 11:26:06 -0700388 def test_UnzipTemp(self):
389 zip_file = self._test_UnzipTemp_createZipFile()
390 unzipped_dir = common.UnzipTemp(zip_file)
391 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
392 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
393 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
394 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
395 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
396
Tao Bao82490d32019-04-09 00:12:30 -0700397 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao0ff15de2019-03-20 11:26:06 -0700398 def test_UnzipTemp_withPatterns(self):
399 zip_file = self._test_UnzipTemp_createZipFile()
400
401 unzipped_dir = common.UnzipTemp(zip_file, ['Test1'])
402 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
403 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
404 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
405 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
406 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
407
408 unzipped_dir = common.UnzipTemp(zip_file, ['Test1', 'Foo3'])
409 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
410 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
411 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
412 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
413 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
414
415 unzipped_dir = common.UnzipTemp(zip_file, ['Test*', 'Foo3*'])
416 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
417 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
418 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
419 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
420 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
421
422 unzipped_dir = common.UnzipTemp(zip_file, ['*Test1', '*Baz*'])
423 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
424 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
425 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
426 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
427 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
428
429 def test_UnzipTemp_withEmptyPatterns(self):
430 zip_file = self._test_UnzipTemp_createZipFile()
431 unzipped_dir = common.UnzipTemp(zip_file, [])
432 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
433 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
434 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
435 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
436 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
437
Tao Bao82490d32019-04-09 00:12:30 -0700438 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao0ff15de2019-03-20 11:26:06 -0700439 def test_UnzipTemp_withPartiallyMatchingPatterns(self):
440 zip_file = self._test_UnzipTemp_createZipFile()
441 unzipped_dir = common.UnzipTemp(zip_file, ['Test*', 'Nonexistent*'])
442 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
443 self.assertTrue(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
444 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
445 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
446 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
447
448 def test_UnzipTemp_withNoMatchingPatterns(self):
449 zip_file = self._test_UnzipTemp_createZipFile()
450 unzipped_dir = common.UnzipTemp(zip_file, ['Foo4', 'Nonexistent*'])
451 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Test1')))
452 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Test2')))
453 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Foo3')))
454 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Bar4')))
455 self.assertFalse(os.path.exists(os.path.join(unzipped_dir, 'Dir5/Baz5')))
456
Tao Bao89d7ab22017-12-14 17:05:33 -0800457
Tao Bao65b94e92018-10-11 21:57:26 -0700458class CommonApkUtilsTest(test_utils.ReleaseToolsTestCase):
Tao Bao818ddf52018-01-05 11:17:34 -0800459 """Tests the APK utils related functions."""
460
461 APKCERTS_TXT1 = (
462 'name="RecoveryLocalizer.apk" certificate="certs/devkey.x509.pem"'
463 ' private_key="certs/devkey.pk8"\n'
464 'name="Settings.apk"'
Dan Willemsen0ab1be62019-04-09 21:35:37 -0700465 ' certificate="build/make/target/product/security/platform.x509.pem"'
466 ' private_key="build/make/target/product/security/platform.pk8"\n'
Tao Bao818ddf52018-01-05 11:17:34 -0800467 'name="TV.apk" certificate="PRESIGNED" private_key=""\n'
468 )
469
470 APKCERTS_CERTMAP1 = {
471 'RecoveryLocalizer.apk' : 'certs/devkey',
Dan Willemsen0ab1be62019-04-09 21:35:37 -0700472 'Settings.apk' : 'build/make/target/product/security/platform',
Tao Bao818ddf52018-01-05 11:17:34 -0800473 'TV.apk' : 'PRESIGNED',
474 }
475
476 APKCERTS_TXT2 = (
477 'name="Compressed1.apk" certificate="certs/compressed1.x509.pem"'
478 ' private_key="certs/compressed1.pk8" compressed="gz"\n'
479 'name="Compressed2a.apk" certificate="certs/compressed2.x509.pem"'
480 ' private_key="certs/compressed2.pk8" compressed="gz"\n'
481 'name="Compressed2b.apk" certificate="certs/compressed2.x509.pem"'
482 ' private_key="certs/compressed2.pk8" compressed="gz"\n'
483 'name="Compressed3.apk" certificate="certs/compressed3.x509.pem"'
484 ' private_key="certs/compressed3.pk8" compressed="gz"\n'
485 )
486
487 APKCERTS_CERTMAP2 = {
488 'Compressed1.apk' : 'certs/compressed1',
489 'Compressed2a.apk' : 'certs/compressed2',
490 'Compressed2b.apk' : 'certs/compressed2',
491 'Compressed3.apk' : 'certs/compressed3',
492 }
493
494 APKCERTS_TXT3 = (
495 'name="Compressed4.apk" certificate="certs/compressed4.x509.pem"'
496 ' private_key="certs/compressed4.pk8" compressed="xz"\n'
497 )
498
499 APKCERTS_CERTMAP3 = {
500 'Compressed4.apk' : 'certs/compressed4',
501 }
502
Tao Bao17e4e612018-02-16 17:12:54 -0800503 def setUp(self):
504 self.testdata_dir = test_utils.get_testdata_dir()
505
Tao Bao818ddf52018-01-05 11:17:34 -0800506 @staticmethod
507 def _write_apkcerts_txt(apkcerts_txt, additional=None):
508 if additional is None:
509 additional = []
510 target_files = common.MakeTempFile(suffix='.zip')
511 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
512 target_files_zip.writestr('META/apkcerts.txt', apkcerts_txt)
513 for entry in additional:
514 target_files_zip.writestr(entry, '')
515 return target_files
516
517 def test_ReadApkCerts_NoncompressedApks(self):
518 target_files = self._write_apkcerts_txt(self.APKCERTS_TXT1)
519 with zipfile.ZipFile(target_files, 'r') as input_zip:
520 certmap, ext = common.ReadApkCerts(input_zip)
521
522 self.assertDictEqual(self.APKCERTS_CERTMAP1, certmap)
523 self.assertIsNone(ext)
524
525 def test_ReadApkCerts_CompressedApks(self):
526 # We have "installed" Compressed1.apk.gz only. Note that Compressed3.apk is
527 # not stored in '.gz' format, so it shouldn't be considered as installed.
528 target_files = self._write_apkcerts_txt(
529 self.APKCERTS_TXT2,
530 ['Compressed1.apk.gz', 'Compressed3.apk'])
531
532 with zipfile.ZipFile(target_files, 'r') as input_zip:
533 certmap, ext = common.ReadApkCerts(input_zip)
534
535 self.assertDictEqual(self.APKCERTS_CERTMAP2, certmap)
536 self.assertEqual('.gz', ext)
537
538 # Alternative case with '.xz'.
539 target_files = self._write_apkcerts_txt(
540 self.APKCERTS_TXT3, ['Compressed4.apk.xz'])
541
542 with zipfile.ZipFile(target_files, 'r') as input_zip:
543 certmap, ext = common.ReadApkCerts(input_zip)
544
545 self.assertDictEqual(self.APKCERTS_CERTMAP3, certmap)
546 self.assertEqual('.xz', ext)
547
548 def test_ReadApkCerts_CompressedAndNoncompressedApks(self):
549 target_files = self._write_apkcerts_txt(
550 self.APKCERTS_TXT1 + self.APKCERTS_TXT2,
551 ['Compressed1.apk.gz', 'Compressed3.apk'])
552
553 with zipfile.ZipFile(target_files, 'r') as input_zip:
554 certmap, ext = common.ReadApkCerts(input_zip)
555
556 certmap_merged = self.APKCERTS_CERTMAP1.copy()
557 certmap_merged.update(self.APKCERTS_CERTMAP2)
558 self.assertDictEqual(certmap_merged, certmap)
559 self.assertEqual('.gz', ext)
560
561 def test_ReadApkCerts_MultipleCompressionMethods(self):
562 target_files = self._write_apkcerts_txt(
563 self.APKCERTS_TXT2 + self.APKCERTS_TXT3,
564 ['Compressed1.apk.gz', 'Compressed4.apk.xz'])
565
566 with zipfile.ZipFile(target_files, 'r') as input_zip:
567 self.assertRaises(ValueError, common.ReadApkCerts, input_zip)
568
569 def test_ReadApkCerts_MismatchingKeys(self):
570 malformed_apkcerts_txt = (
571 'name="App1.apk" certificate="certs/cert1.x509.pem"'
572 ' private_key="certs/cert2.pk8"\n'
573 )
574 target_files = self._write_apkcerts_txt(malformed_apkcerts_txt)
575
576 with zipfile.ZipFile(target_files, 'r') as input_zip:
577 self.assertRaises(ValueError, common.ReadApkCerts, input_zip)
578
Tao Bao04e1f012018-02-04 12:13:35 -0800579 def test_ExtractPublicKey(self):
Tao Bao17e4e612018-02-16 17:12:54 -0800580 cert = os.path.join(self.testdata_dir, 'testkey.x509.pem')
581 pubkey = os.path.join(self.testdata_dir, 'testkey.pubkey.pem')
Tao Baoda30cfa2017-12-01 16:19:46 -0800582 with open(pubkey) as pubkey_fp:
Tao Bao04e1f012018-02-04 12:13:35 -0800583 self.assertEqual(pubkey_fp.read(), common.ExtractPublicKey(cert))
584
585 def test_ExtractPublicKey_invalidInput(self):
Tao Bao17e4e612018-02-16 17:12:54 -0800586 wrong_input = os.path.join(self.testdata_dir, 'testkey.pk8')
Tao Bao04e1f012018-02-04 12:13:35 -0800587 self.assertRaises(AssertionError, common.ExtractPublicKey, wrong_input)
588
Tao Bao82490d32019-04-09 00:12:30 -0700589 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao2cc0ca12019-03-15 10:44:43 -0700590 def test_ExtractAvbPublicKey(self):
591 privkey = os.path.join(self.testdata_dir, 'testkey.key')
592 pubkey = os.path.join(self.testdata_dir, 'testkey.pubkey.pem')
Tao Bao1ac886e2019-06-26 11:58:22 -0700593 extracted_from_privkey = common.ExtractAvbPublicKey('avbtool', privkey)
594 extracted_from_pubkey = common.ExtractAvbPublicKey('avbtool', pubkey)
595 with open(extracted_from_privkey, 'rb') as privkey_fp, \
596 open(extracted_from_pubkey, 'rb') as pubkey_fp:
Tao Bao2cc0ca12019-03-15 10:44:43 -0700597 self.assertEqual(privkey_fp.read(), pubkey_fp.read())
598
Tao Bao17e4e612018-02-16 17:12:54 -0800599 def test_ParseCertificate(self):
600 cert = os.path.join(self.testdata_dir, 'testkey.x509.pem')
601
602 cmd = ['openssl', 'x509', '-in', cert, '-outform', 'DER']
Tao Baoda30cfa2017-12-01 16:19:46 -0800603 proc = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
604 universal_newlines=False)
Tao Bao17e4e612018-02-16 17:12:54 -0800605 expected, _ = proc.communicate()
606 self.assertEqual(0, proc.returncode)
607
608 with open(cert) as cert_fp:
609 actual = common.ParseCertificate(cert_fp.read())
610 self.assertEqual(expected, actual)
611
Tao Bao82490d32019-04-09 00:12:30 -0700612 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baof47bf0f2018-03-21 23:28:51 -0700613 def test_GetMinSdkVersion(self):
614 test_app = os.path.join(self.testdata_dir, 'TestApp.apk')
615 self.assertEqual('24', common.GetMinSdkVersion(test_app))
616
Tao Bao82490d32019-04-09 00:12:30 -0700617 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baof47bf0f2018-03-21 23:28:51 -0700618 def test_GetMinSdkVersion_invalidInput(self):
619 self.assertRaises(
620 common.ExternalError, common.GetMinSdkVersion, 'does-not-exist.apk')
621
Tao Bao82490d32019-04-09 00:12:30 -0700622 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baof47bf0f2018-03-21 23:28:51 -0700623 def test_GetMinSdkVersionInt(self):
624 test_app = os.path.join(self.testdata_dir, 'TestApp.apk')
625 self.assertEqual(24, common.GetMinSdkVersionInt(test_app, {}))
626
Tao Bao82490d32019-04-09 00:12:30 -0700627 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baof47bf0f2018-03-21 23:28:51 -0700628 def test_GetMinSdkVersionInt_invalidInput(self):
629 self.assertRaises(
630 common.ExternalError, common.GetMinSdkVersionInt, 'does-not-exist.apk',
631 {})
632
Tao Bao818ddf52018-01-05 11:17:34 -0800633
Tao Bao65b94e92018-10-11 21:57:26 -0700634class CommonUtilsTest(test_utils.ReleaseToolsTestCase):
Tao Baofc7e0e02018-02-13 13:54:02 -0800635
Tao Bao02a08592018-07-22 12:40:45 -0700636 def setUp(self):
637 self.testdata_dir = test_utils.get_testdata_dir()
638
Tao Bao82490d32019-04-09 00:12:30 -0700639 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baofc7e0e02018-02-13 13:54:02 -0800640 def test_GetSparseImage_emptyBlockMapFile(self):
641 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
642 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
643 target_files_zip.write(
644 test_utils.construct_sparse_image([
645 (0xCAC1, 6),
646 (0xCAC3, 3),
647 (0xCAC1, 4)]),
648 arcname='IMAGES/system.img')
649 target_files_zip.writestr('IMAGES/system.map', '')
650 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 8))
651 target_files_zip.writestr('SYSTEM/file2', os.urandom(4096 * 3))
652
Tao Baodba59ee2018-01-09 13:21:02 -0800653 tempdir = common.UnzipTemp(target_files)
654 with zipfile.ZipFile(target_files, 'r') as input_zip:
655 sparse_image = common.GetSparseImage('system', tempdir, input_zip, False)
Tao Baofc7e0e02018-02-13 13:54:02 -0800656
657 self.assertDictEqual(
658 {
659 '__COPY': RangeSet("0"),
660 '__NONZERO-0': RangeSet("1-5 9-12"),
661 },
662 sparse_image.file_map)
663
Tao Baob2de7d92019-04-10 10:01:47 -0700664 def test_GetSparseImage_missingImageFile(self):
Tao Baofc7e0e02018-02-13 13:54:02 -0800665 self.assertRaises(
Tao Baob2de7d92019-04-10 10:01:47 -0700666 AssertionError, common.GetSparseImage, 'system2', self.testdata_dir,
667 None, False)
Tao Baofc7e0e02018-02-13 13:54:02 -0800668 self.assertRaises(
Tao Baob2de7d92019-04-10 10:01:47 -0700669 AssertionError, common.GetSparseImage, 'unknown', self.testdata_dir,
670 None, False)
Tao Baofc7e0e02018-02-13 13:54:02 -0800671
Tao Bao82490d32019-04-09 00:12:30 -0700672 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baofc7e0e02018-02-13 13:54:02 -0800673 def test_GetSparseImage_missingBlockMapFile(self):
674 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
675 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
676 target_files_zip.write(
677 test_utils.construct_sparse_image([
678 (0xCAC1, 6),
679 (0xCAC3, 3),
680 (0xCAC1, 4)]),
681 arcname='IMAGES/system.img')
682 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 8))
683 target_files_zip.writestr('SYSTEM/file2', os.urandom(4096 * 3))
684
Tao Baodba59ee2018-01-09 13:21:02 -0800685 tempdir = common.UnzipTemp(target_files)
686 with zipfile.ZipFile(target_files, 'r') as input_zip:
687 self.assertRaises(
688 AssertionError, common.GetSparseImage, 'system', tempdir, input_zip,
689 False)
Tao Baofc7e0e02018-02-13 13:54:02 -0800690
Tao Bao82490d32019-04-09 00:12:30 -0700691 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baofc7e0e02018-02-13 13:54:02 -0800692 def test_GetSparseImage_sharedBlocks_notAllowed(self):
693 """Tests the case of having overlapping blocks but disallowed."""
694 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
695 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
696 target_files_zip.write(
697 test_utils.construct_sparse_image([(0xCAC2, 16)]),
698 arcname='IMAGES/system.img')
699 # Block 10 is shared between two files.
700 target_files_zip.writestr(
701 'IMAGES/system.map',
702 '\n'.join([
703 '/system/file1 1-5 9-10',
704 '/system/file2 10-12']))
705 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 7))
706 target_files_zip.writestr('SYSTEM/file2', os.urandom(4096 * 3))
707
Tao Baodba59ee2018-01-09 13:21:02 -0800708 tempdir = common.UnzipTemp(target_files)
709 with zipfile.ZipFile(target_files, 'r') as input_zip:
710 self.assertRaises(
711 AssertionError, common.GetSparseImage, 'system', tempdir, input_zip,
712 False)
Tao Baofc7e0e02018-02-13 13:54:02 -0800713
Tao Bao82490d32019-04-09 00:12:30 -0700714 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baofc7e0e02018-02-13 13:54:02 -0800715 def test_GetSparseImage_sharedBlocks_allowed(self):
716 """Tests the case for target using BOARD_EXT4_SHARE_DUP_BLOCKS := true."""
717 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
718 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
719 # Construct an image with a care_map of "0-5 9-12".
720 target_files_zip.write(
721 test_utils.construct_sparse_image([(0xCAC2, 16)]),
722 arcname='IMAGES/system.img')
723 # Block 10 is shared between two files.
724 target_files_zip.writestr(
725 'IMAGES/system.map',
726 '\n'.join([
727 '/system/file1 1-5 9-10',
728 '/system/file2 10-12']))
729 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 7))
730 target_files_zip.writestr('SYSTEM/file2', os.urandom(4096 * 3))
731
Tao Baodba59ee2018-01-09 13:21:02 -0800732 tempdir = common.UnzipTemp(target_files)
733 with zipfile.ZipFile(target_files, 'r') as input_zip:
734 sparse_image = common.GetSparseImage('system', tempdir, input_zip, True)
Tao Baofc7e0e02018-02-13 13:54:02 -0800735
736 self.assertDictEqual(
737 {
738 '__COPY': RangeSet("0"),
739 '__NONZERO-0': RangeSet("6-8 13-15"),
740 '/system/file1': RangeSet("1-5 9-10"),
741 '/system/file2': RangeSet("11-12"),
742 },
743 sparse_image.file_map)
744
745 # '/system/file2' should be marked with 'uses_shared_blocks', but not with
746 # 'incomplete'.
747 self.assertTrue(
748 sparse_image.file_map['/system/file2'].extra['uses_shared_blocks'])
749 self.assertNotIn(
750 'incomplete', sparse_image.file_map['/system/file2'].extra)
751
Tao Baoa264fef2019-10-06 21:55:20 -0700752 # '/system/file1' will only contain one field -- a copy of the input text.
753 self.assertEqual(1, len(sparse_image.file_map['/system/file1'].extra))
754
755 # Meta entries should not have any extra tag.
Tao Baofc7e0e02018-02-13 13:54:02 -0800756 self.assertFalse(sparse_image.file_map['__COPY'].extra)
757 self.assertFalse(sparse_image.file_map['__NONZERO-0'].extra)
Tao Baofc7e0e02018-02-13 13:54:02 -0800758
Tao Bao82490d32019-04-09 00:12:30 -0700759 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baofc7e0e02018-02-13 13:54:02 -0800760 def test_GetSparseImage_incompleteRanges(self):
761 """Tests the case of ext4 images with holes."""
762 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
763 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
764 target_files_zip.write(
765 test_utils.construct_sparse_image([(0xCAC2, 16)]),
766 arcname='IMAGES/system.img')
767 target_files_zip.writestr(
768 'IMAGES/system.map',
769 '\n'.join([
770 '/system/file1 1-5 9-10',
771 '/system/file2 11-12']))
772 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 7))
773 # '/system/file2' has less blocks listed (2) than actual (3).
774 target_files_zip.writestr('SYSTEM/file2', os.urandom(4096 * 3))
775
Tao Baodba59ee2018-01-09 13:21:02 -0800776 tempdir = common.UnzipTemp(target_files)
777 with zipfile.ZipFile(target_files, 'r') as input_zip:
778 sparse_image = common.GetSparseImage('system', tempdir, input_zip, False)
Tao Baofc7e0e02018-02-13 13:54:02 -0800779
Tao Baoa264fef2019-10-06 21:55:20 -0700780 self.assertEqual(
781 '1-5 9-10',
782 sparse_image.file_map['/system/file1'].extra['text_str'])
Tao Baofc7e0e02018-02-13 13:54:02 -0800783 self.assertTrue(sparse_image.file_map['/system/file2'].extra['incomplete'])
784
Tao Bao82490d32019-04-09 00:12:30 -0700785 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baod3554e62018-07-10 15:31:22 -0700786 def test_GetSparseImage_systemRootImage_filenameWithExtraLeadingSlash(self):
787 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
788 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
789 target_files_zip.write(
790 test_utils.construct_sparse_image([(0xCAC2, 16)]),
791 arcname='IMAGES/system.img')
792 target_files_zip.writestr(
793 'IMAGES/system.map',
794 '\n'.join([
795 '//system/file1 1-5 9-10',
796 '//system/file2 11-12',
797 '/system/app/file3 13-15']))
798 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 7))
799 # '/system/file2' has less blocks listed (2) than actual (3).
800 target_files_zip.writestr('SYSTEM/file2', os.urandom(4096 * 3))
801 # '/system/app/file3' has less blocks listed (3) than actual (4).
802 target_files_zip.writestr('SYSTEM/app/file3', os.urandom(4096 * 4))
803
804 tempdir = common.UnzipTemp(target_files)
805 with zipfile.ZipFile(target_files, 'r') as input_zip:
806 sparse_image = common.GetSparseImage('system', tempdir, input_zip, False)
807
Tao Baoa264fef2019-10-06 21:55:20 -0700808 self.assertEqual(
809 '1-5 9-10',
810 sparse_image.file_map['//system/file1'].extra['text_str'])
Tao Baod3554e62018-07-10 15:31:22 -0700811 self.assertTrue(sparse_image.file_map['//system/file2'].extra['incomplete'])
812 self.assertTrue(
813 sparse_image.file_map['/system/app/file3'].extra['incomplete'])
814
Tao Bao82490d32019-04-09 00:12:30 -0700815 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baod3554e62018-07-10 15:31:22 -0700816 def test_GetSparseImage_systemRootImage_nonSystemFiles(self):
817 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
818 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
819 target_files_zip.write(
820 test_utils.construct_sparse_image([(0xCAC2, 16)]),
821 arcname='IMAGES/system.img')
822 target_files_zip.writestr(
823 'IMAGES/system.map',
824 '\n'.join([
825 '//system/file1 1-5 9-10',
826 '//init.rc 13-15']))
827 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 7))
828 # '/init.rc' has less blocks listed (3) than actual (4).
829 target_files_zip.writestr('ROOT/init.rc', os.urandom(4096 * 4))
830
831 tempdir = common.UnzipTemp(target_files)
832 with zipfile.ZipFile(target_files, 'r') as input_zip:
833 sparse_image = common.GetSparseImage('system', tempdir, input_zip, False)
834
Tao Baoa264fef2019-10-06 21:55:20 -0700835 self.assertEqual(
836 '1-5 9-10',
837 sparse_image.file_map['//system/file1'].extra['text_str'])
Tao Baod3554e62018-07-10 15:31:22 -0700838 self.assertTrue(sparse_image.file_map['//init.rc'].extra['incomplete'])
839
Tao Bao82490d32019-04-09 00:12:30 -0700840 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baod3554e62018-07-10 15:31:22 -0700841 def test_GetSparseImage_fileNotFound(self):
842 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
843 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
844 target_files_zip.write(
845 test_utils.construct_sparse_image([(0xCAC2, 16)]),
846 arcname='IMAGES/system.img')
847 target_files_zip.writestr(
848 'IMAGES/system.map',
849 '\n'.join([
850 '//system/file1 1-5 9-10',
851 '//system/file2 11-12']))
852 target_files_zip.writestr('SYSTEM/file1', os.urandom(4096 * 7))
853
854 tempdir = common.UnzipTemp(target_files)
855 with zipfile.ZipFile(target_files, 'r') as input_zip:
856 self.assertRaises(
857 AssertionError, common.GetSparseImage, 'system', tempdir, input_zip,
858 False)
859
Tao Bao82490d32019-04-09 00:12:30 -0700860 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao02a08592018-07-22 12:40:45 -0700861 def test_GetAvbChainedPartitionArg(self):
862 pubkey = os.path.join(self.testdata_dir, 'testkey.pubkey.pem')
863 info_dict = {
864 'avb_avbtool': 'avbtool',
865 'avb_system_key_path': pubkey,
866 'avb_system_rollback_index_location': 2,
867 }
868 args = common.GetAvbChainedPartitionArg('system', info_dict).split(':')
869 self.assertEqual(3, len(args))
870 self.assertEqual('system', args[0])
871 self.assertEqual('2', args[1])
872 self.assertTrue(os.path.exists(args[2]))
873
Tao Bao82490d32019-04-09 00:12:30 -0700874 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao02a08592018-07-22 12:40:45 -0700875 def test_GetAvbChainedPartitionArg_withPrivateKey(self):
876 key = os.path.join(self.testdata_dir, 'testkey.key')
877 info_dict = {
878 'avb_avbtool': 'avbtool',
879 'avb_product_key_path': key,
880 'avb_product_rollback_index_location': 2,
881 }
882 args = common.GetAvbChainedPartitionArg('product', info_dict).split(':')
883 self.assertEqual(3, len(args))
884 self.assertEqual('product', args[0])
885 self.assertEqual('2', args[1])
886 self.assertTrue(os.path.exists(args[2]))
887
Tao Bao82490d32019-04-09 00:12:30 -0700888 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao02a08592018-07-22 12:40:45 -0700889 def test_GetAvbChainedPartitionArg_withSpecifiedKey(self):
890 info_dict = {
891 'avb_avbtool': 'avbtool',
892 'avb_system_key_path': 'does-not-exist',
893 'avb_system_rollback_index_location': 2,
894 }
895 pubkey = os.path.join(self.testdata_dir, 'testkey.pubkey.pem')
896 args = common.GetAvbChainedPartitionArg(
897 'system', info_dict, pubkey).split(':')
898 self.assertEqual(3, len(args))
899 self.assertEqual('system', args[0])
900 self.assertEqual('2', args[1])
901 self.assertTrue(os.path.exists(args[2]))
902
Tao Bao82490d32019-04-09 00:12:30 -0700903 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao02a08592018-07-22 12:40:45 -0700904 def test_GetAvbChainedPartitionArg_invalidKey(self):
905 pubkey = os.path.join(self.testdata_dir, 'testkey_with_passwd.x509.pem')
906 info_dict = {
907 'avb_avbtool': 'avbtool',
908 'avb_system_key_path': pubkey,
909 'avb_system_rollback_index_location': 2,
910 }
911 self.assertRaises(
Tao Bao986ee862018-10-04 15:46:16 -0700912 common.ExternalError, common.GetAvbChainedPartitionArg, 'system',
913 info_dict)
Tao Bao02a08592018-07-22 12:40:45 -0700914
Tao Baoa57ab9f2018-08-24 12:08:38 -0700915 INFO_DICT_DEFAULT = {
916 'recovery_api_version': 3,
917 'fstab_version': 2,
918 'system_root_image': 'true',
919 'no_recovery' : 'true',
920 'recovery_as_boot': 'true',
921 }
922
Daniel Norman4cc9df62019-07-18 10:11:07 -0700923 def test_LoadListFromFile(self):
924 file_path = os.path.join(self.testdata_dir,
925 'merge_config_framework_item_list')
926 contents = common.LoadListFromFile(file_path)
927 expected_contents = [
928 'META/apkcerts.txt',
929 'META/filesystem_config.txt',
930 'META/root_filesystem_config.txt',
931 'META/system_manifest.xml',
932 'META/system_matrix.xml',
933 'META/update_engine_config.txt',
934 'PRODUCT/*',
935 'ROOT/*',
936 'SYSTEM/*',
937 ]
938 self.assertEqual(sorted(contents), sorted(expected_contents))
939
Tao Baoa57ab9f2018-08-24 12:08:38 -0700940 @staticmethod
941 def _test_LoadInfoDict_createTargetFiles(info_dict, fstab_path):
942 target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
943 with zipfile.ZipFile(target_files, 'w') as target_files_zip:
944 info_values = ''.join(
Tao Baoda30cfa2017-12-01 16:19:46 -0800945 ['{}={}\n'.format(k, v) for k, v in sorted(info_dict.items())])
Tao Baoa57ab9f2018-08-24 12:08:38 -0700946 common.ZipWriteStr(target_files_zip, 'META/misc_info.txt', info_values)
947
948 FSTAB_TEMPLATE = "/dev/block/system {} ext4 ro,barrier=1 defaults"
949 if info_dict.get('system_root_image') == 'true':
950 fstab_values = FSTAB_TEMPLATE.format('/')
951 else:
952 fstab_values = FSTAB_TEMPLATE.format('/system')
953 common.ZipWriteStr(target_files_zip, fstab_path, fstab_values)
Tao Bao410ad8b2018-08-24 12:08:38 -0700954
955 common.ZipWriteStr(
956 target_files_zip, 'META/file_contexts', 'file-contexts')
Tao Baoa57ab9f2018-08-24 12:08:38 -0700957 return target_files
958
959 def test_LoadInfoDict(self):
960 target_files = self._test_LoadInfoDict_createTargetFiles(
961 self.INFO_DICT_DEFAULT,
962 'BOOT/RAMDISK/system/etc/recovery.fstab')
963 with zipfile.ZipFile(target_files, 'r') as target_files_zip:
964 loaded_dict = common.LoadInfoDict(target_files_zip)
965 self.assertEqual(3, loaded_dict['recovery_api_version'])
966 self.assertEqual(2, loaded_dict['fstab_version'])
967 self.assertIn('/', loaded_dict['fstab'])
968 self.assertIn('/system', loaded_dict['fstab'])
969
970 def test_LoadInfoDict_legacyRecoveryFstabPath(self):
971 target_files = self._test_LoadInfoDict_createTargetFiles(
972 self.INFO_DICT_DEFAULT,
973 'BOOT/RAMDISK/etc/recovery.fstab')
974 with zipfile.ZipFile(target_files, 'r') as target_files_zip:
975 loaded_dict = common.LoadInfoDict(target_files_zip)
976 self.assertEqual(3, loaded_dict['recovery_api_version'])
977 self.assertEqual(2, loaded_dict['fstab_version'])
978 self.assertIn('/', loaded_dict['fstab'])
979 self.assertIn('/system', loaded_dict['fstab'])
980
Tao Bao82490d32019-04-09 00:12:30 -0700981 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baoa57ab9f2018-08-24 12:08:38 -0700982 def test_LoadInfoDict_dirInput(self):
983 target_files = self._test_LoadInfoDict_createTargetFiles(
984 self.INFO_DICT_DEFAULT,
985 'BOOT/RAMDISK/system/etc/recovery.fstab')
986 unzipped = common.UnzipTemp(target_files)
987 loaded_dict = common.LoadInfoDict(unzipped)
988 self.assertEqual(3, loaded_dict['recovery_api_version'])
989 self.assertEqual(2, loaded_dict['fstab_version'])
990 self.assertIn('/', loaded_dict['fstab'])
991 self.assertIn('/system', loaded_dict['fstab'])
992
Tao Bao82490d32019-04-09 00:12:30 -0700993 @test_utils.SkipIfExternalToolsUnavailable()
Tao Baoa57ab9f2018-08-24 12:08:38 -0700994 def test_LoadInfoDict_dirInput_legacyRecoveryFstabPath(self):
995 target_files = self._test_LoadInfoDict_createTargetFiles(
996 self.INFO_DICT_DEFAULT,
997 'BOOT/RAMDISK/system/etc/recovery.fstab')
998 unzipped = common.UnzipTemp(target_files)
999 loaded_dict = common.LoadInfoDict(unzipped)
1000 self.assertEqual(3, loaded_dict['recovery_api_version'])
1001 self.assertEqual(2, loaded_dict['fstab_version'])
1002 self.assertIn('/', loaded_dict['fstab'])
1003 self.assertIn('/system', loaded_dict['fstab'])
1004
1005 def test_LoadInfoDict_systemRootImageFalse(self):
1006 # Devices not using system-as-root nor recovery-as-boot. Non-A/B devices
1007 # launched prior to P will likely have this config.
1008 info_dict = copy.copy(self.INFO_DICT_DEFAULT)
1009 del info_dict['no_recovery']
1010 del info_dict['system_root_image']
1011 del info_dict['recovery_as_boot']
1012 target_files = self._test_LoadInfoDict_createTargetFiles(
1013 info_dict,
1014 'RECOVERY/RAMDISK/system/etc/recovery.fstab')
1015 with zipfile.ZipFile(target_files, 'r') as target_files_zip:
1016 loaded_dict = common.LoadInfoDict(target_files_zip)
1017 self.assertEqual(3, loaded_dict['recovery_api_version'])
1018 self.assertEqual(2, loaded_dict['fstab_version'])
1019 self.assertNotIn('/', loaded_dict['fstab'])
1020 self.assertIn('/system', loaded_dict['fstab'])
1021
1022 def test_LoadInfoDict_recoveryAsBootFalse(self):
1023 # Devices using system-as-root, but with standalone recovery image. Non-A/B
1024 # devices launched since P will likely have this config.
1025 info_dict = copy.copy(self.INFO_DICT_DEFAULT)
1026 del info_dict['no_recovery']
1027 del info_dict['recovery_as_boot']
1028 target_files = self._test_LoadInfoDict_createTargetFiles(
1029 info_dict,
1030 'RECOVERY/RAMDISK/system/etc/recovery.fstab')
1031 with zipfile.ZipFile(target_files, 'r') as target_files_zip:
1032 loaded_dict = common.LoadInfoDict(target_files_zip)
1033 self.assertEqual(3, loaded_dict['recovery_api_version'])
1034 self.assertEqual(2, loaded_dict['fstab_version'])
1035 self.assertIn('/', loaded_dict['fstab'])
1036 self.assertIn('/system', loaded_dict['fstab'])
1037
1038 def test_LoadInfoDict_noRecoveryTrue(self):
1039 # Device doesn't have a recovery partition at all.
1040 info_dict = copy.copy(self.INFO_DICT_DEFAULT)
1041 del info_dict['recovery_as_boot']
1042 target_files = self._test_LoadInfoDict_createTargetFiles(
1043 info_dict,
1044 'RECOVERY/RAMDISK/system/etc/recovery.fstab')
1045 with zipfile.ZipFile(target_files, 'r') as target_files_zip:
1046 loaded_dict = common.LoadInfoDict(target_files_zip)
1047 self.assertEqual(3, loaded_dict['recovery_api_version'])
1048 self.assertEqual(2, loaded_dict['fstab_version'])
1049 self.assertIsNone(loaded_dict['fstab'])
1050
Tao Bao82490d32019-04-09 00:12:30 -07001051 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao410ad8b2018-08-24 12:08:38 -07001052 def test_LoadInfoDict_missingMetaMiscInfoTxt(self):
1053 target_files = self._test_LoadInfoDict_createTargetFiles(
1054 self.INFO_DICT_DEFAULT,
1055 'BOOT/RAMDISK/system/etc/recovery.fstab')
1056 common.ZipDelete(target_files, 'META/misc_info.txt')
1057 with zipfile.ZipFile(target_files, 'r') as target_files_zip:
1058 self.assertRaises(ValueError, common.LoadInfoDict, target_files_zip)
1059
Tao Bao82490d32019-04-09 00:12:30 -07001060 @test_utils.SkipIfExternalToolsUnavailable()
Tao Bao410ad8b2018-08-24 12:08:38 -07001061 def test_LoadInfoDict_repacking(self):
1062 target_files = self._test_LoadInfoDict_createTargetFiles(
1063 self.INFO_DICT_DEFAULT,
1064 'BOOT/RAMDISK/system/etc/recovery.fstab')
1065 unzipped = common.UnzipTemp(target_files)
1066 loaded_dict = common.LoadInfoDict(unzipped, True)
1067 self.assertEqual(3, loaded_dict['recovery_api_version'])
1068 self.assertEqual(2, loaded_dict['fstab_version'])
1069 self.assertIn('/', loaded_dict['fstab'])
1070 self.assertIn('/system', loaded_dict['fstab'])
1071 self.assertEqual(
1072 os.path.join(unzipped, 'ROOT'), loaded_dict['root_dir'])
1073 self.assertEqual(
1074 os.path.join(unzipped, 'META', 'root_filesystem_config.txt'),
1075 loaded_dict['root_fs_config'])
1076
1077 def test_LoadInfoDict_repackingWithZipFileInput(self):
1078 target_files = self._test_LoadInfoDict_createTargetFiles(
1079 self.INFO_DICT_DEFAULT,
1080 'BOOT/RAMDISK/system/etc/recovery.fstab')
1081 with zipfile.ZipFile(target_files, 'r') as target_files_zip:
1082 self.assertRaises(
1083 AssertionError, common.LoadInfoDict, target_files_zip, True)
1084
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001085 def test_MergeDynamicPartitionInfoDicts_ReturnsMergedDict(self):
1086 framework_dict = {
1087 'super_partition_groups': 'group_a',
1088 'dynamic_partition_list': 'system',
1089 'super_group_a_list': 'system',
1090 }
1091 vendor_dict = {
1092 'super_partition_groups': 'group_a group_b',
1093 'dynamic_partition_list': 'vendor product',
1094 'super_group_a_list': 'vendor',
1095 'super_group_a_size': '1000',
1096 'super_group_b_list': 'product',
1097 'super_group_b_size': '2000',
1098 }
1099 merged_dict = common.MergeDynamicPartitionInfoDicts(
1100 framework_dict=framework_dict,
1101 vendor_dict=vendor_dict,
1102 size_prefix='super_',
1103 size_suffix='_size',
1104 list_prefix='super_',
1105 list_suffix='_list')
1106 expected_merged_dict = {
1107 'super_partition_groups': 'group_a group_b',
1108 'dynamic_partition_list': 'system vendor product',
1109 'super_group_a_list': 'system vendor',
1110 'super_group_a_size': '1000',
1111 'super_group_b_list': 'product',
1112 'super_group_b_size': '2000',
1113 }
1114 self.assertEqual(merged_dict, expected_merged_dict)
1115
1116 def test_MergeDynamicPartitionInfoDicts_IgnoringFrameworkGroupSize(self):
1117 framework_dict = {
1118 'super_partition_groups': 'group_a',
1119 'dynamic_partition_list': 'system',
1120 'super_group_a_list': 'system',
1121 'super_group_a_size': '5000',
1122 }
1123 vendor_dict = {
1124 'super_partition_groups': 'group_a group_b',
1125 'dynamic_partition_list': 'vendor product',
1126 'super_group_a_list': 'vendor',
1127 'super_group_a_size': '1000',
1128 'super_group_b_list': 'product',
1129 'super_group_b_size': '2000',
1130 }
1131 merged_dict = common.MergeDynamicPartitionInfoDicts(
1132 framework_dict=framework_dict,
1133 vendor_dict=vendor_dict,
1134 size_prefix='super_',
1135 size_suffix='_size',
1136 list_prefix='super_',
1137 list_suffix='_list')
1138 expected_merged_dict = {
1139 'super_partition_groups': 'group_a group_b',
1140 'dynamic_partition_list': 'system vendor product',
1141 'super_group_a_list': 'system vendor',
1142 'super_group_a_size': '1000',
1143 'super_group_b_list': 'product',
1144 'super_group_b_size': '2000',
1145 }
1146 self.assertEqual(merged_dict, expected_merged_dict)
1147
Daniel Norman276f0622019-07-26 14:13:51 -07001148 def test_GetAvbPartitionArg(self):
1149 info_dict = {}
1150 cmd = common.GetAvbPartitionArg('system', '/path/to/system.img', info_dict)
1151 self.assertEqual(
1152 ['--include_descriptors_from_image', '/path/to/system.img'], cmd)
1153
1154 @test_utils.SkipIfExternalToolsUnavailable()
1155 def test_AppendVBMetaArgsForPartition_vendorAsChainedPartition(self):
1156 testdata_dir = test_utils.get_testdata_dir()
1157 pubkey = os.path.join(testdata_dir, 'testkey.pubkey.pem')
1158 info_dict = {
1159 'avb_avbtool': 'avbtool',
1160 'avb_vendor_key_path': pubkey,
1161 'avb_vendor_rollback_index_location': 5,
1162 }
1163 cmd = common.GetAvbPartitionArg('vendor', '/path/to/vendor.img', info_dict)
1164 self.assertEqual(2, len(cmd))
1165 self.assertEqual('--chain_partition', cmd[0])
1166 chained_partition_args = cmd[1].split(':')
1167 self.assertEqual(3, len(chained_partition_args))
1168 self.assertEqual('vendor', chained_partition_args[0])
1169 self.assertEqual('5', chained_partition_args[1])
1170 self.assertTrue(os.path.exists(chained_partition_args[2]))
1171
Tao Baofc7e0e02018-02-13 13:54:02 -08001172
Tao Bao65b94e92018-10-11 21:57:26 -07001173class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
Tao Bao1c830bf2017-12-25 10:43:47 -08001174 """Checks the format of install-recovery.sh.
Tianjie Xu9c384d22017-06-20 17:00:55 -07001175
Tao Bao1c830bf2017-12-25 10:43:47 -08001176 Its format should match between common.py and validate_target_files.py.
1177 """
Tianjie Xu9c384d22017-06-20 17:00:55 -07001178
1179 def setUp(self):
Tao Bao1c830bf2017-12-25 10:43:47 -08001180 self._tempdir = common.MakeTempDir()
Tianjie Xu9c384d22017-06-20 17:00:55 -07001181 # Create a dummy dict that contains the fstab info for boot&recovery.
1182 self._info = {"fstab" : {}}
Tao Bao1c830bf2017-12-25 10:43:47 -08001183 dummy_fstab = [
1184 "/dev/soc.0/by-name/boot /boot emmc defaults defaults",
1185 "/dev/soc.0/by-name/recovery /recovery emmc defaults defaults"]
Tao Bao31b08072017-11-08 15:50:59 -08001186 self._info["fstab"] = common.LoadRecoveryFSTab("\n".join, 2, dummy_fstab)
Tianjie Xudf055582017-11-07 12:22:58 -08001187 # Construct the gzipped recovery.img and boot.img
1188 self.recovery_data = bytearray([
1189 0x1f, 0x8b, 0x08, 0x00, 0x81, 0x11, 0x02, 0x5a, 0x00, 0x03, 0x2b, 0x4a,
1190 0x4d, 0xce, 0x2f, 0x4b, 0x2d, 0xaa, 0x04, 0x00, 0xc9, 0x93, 0x43, 0xf3,
1191 0x08, 0x00, 0x00, 0x00
1192 ])
1193 # echo -n "boot" | gzip -f | hd
1194 self.boot_data = bytearray([
1195 0x1f, 0x8b, 0x08, 0x00, 0x8c, 0x12, 0x02, 0x5a, 0x00, 0x03, 0x4b, 0xca,
1196 0xcf, 0x2f, 0x01, 0x00, 0xc4, 0xae, 0xed, 0x46, 0x04, 0x00, 0x00, 0x00
1197 ])
Tianjie Xu9c384d22017-06-20 17:00:55 -07001198
1199 def _out_tmp_sink(self, name, data, prefix="SYSTEM"):
1200 loc = os.path.join(self._tempdir, prefix, name)
1201 if not os.path.exists(os.path.dirname(loc)):
1202 os.makedirs(os.path.dirname(loc))
Tao Baoda30cfa2017-12-01 16:19:46 -08001203 with open(loc, "wb") as f:
Tianjie Xu9c384d22017-06-20 17:00:55 -07001204 f.write(data)
1205
1206 def test_full_recovery(self):
Tao Bao31b08072017-11-08 15:50:59 -08001207 recovery_image = common.File("recovery.img", self.recovery_data)
1208 boot_image = common.File("boot.img", self.boot_data)
Tianjie Xu9c384d22017-06-20 17:00:55 -07001209 self._info["full_recovery_image"] = "true"
1210
1211 common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink,
1212 recovery_image, boot_image, self._info)
1213 validate_target_files.ValidateInstallRecoveryScript(self._tempdir,
1214 self._info)
1215
Tao Bao82490d32019-04-09 00:12:30 -07001216 @test_utils.SkipIfExternalToolsUnavailable()
Tianjie Xu9c384d22017-06-20 17:00:55 -07001217 def test_recovery_from_boot(self):
Tao Bao31b08072017-11-08 15:50:59 -08001218 recovery_image = common.File("recovery.img", self.recovery_data)
Tianjie Xu9c384d22017-06-20 17:00:55 -07001219 self._out_tmp_sink("recovery.img", recovery_image.data, "IMAGES")
Tao Bao31b08072017-11-08 15:50:59 -08001220 boot_image = common.File("boot.img", self.boot_data)
Tianjie Xu9c384d22017-06-20 17:00:55 -07001221 self._out_tmp_sink("boot.img", boot_image.data, "IMAGES")
1222
1223 common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink,
1224 recovery_image, boot_image, self._info)
1225 validate_target_files.ValidateInstallRecoveryScript(self._tempdir,
1226 self._info)
1227 # Validate 'recovery-from-boot' with bonus argument.
Tao Baoda30cfa2017-12-01 16:19:46 -08001228 self._out_tmp_sink("etc/recovery-resource.dat", b"bonus", "SYSTEM")
Tianjie Xu9c384d22017-06-20 17:00:55 -07001229 common.MakeRecoveryPatch(self._tempdir, self._out_tmp_sink,
1230 recovery_image, boot_image, self._info)
1231 validate_target_files.ValidateInstallRecoveryScript(self._tempdir,
1232 self._info)
Yifan Hong45433e42019-01-18 13:55:25 -08001233
1234
1235class MockScriptWriter(object):
Tao Baoda30cfa2017-12-01 16:19:46 -08001236 """A class that mocks edify_generator.EdifyGenerator."""
1237
Yifan Hong45433e42019-01-18 13:55:25 -08001238 def __init__(self, enable_comments=False):
1239 self.lines = []
1240 self.enable_comments = enable_comments
Tao Baoda30cfa2017-12-01 16:19:46 -08001241
Yifan Hong45433e42019-01-18 13:55:25 -08001242 def Comment(self, comment):
1243 if self.enable_comments:
Tao Baoda30cfa2017-12-01 16:19:46 -08001244 self.lines.append('# {}'.format(comment))
1245
Yifan Hong45433e42019-01-18 13:55:25 -08001246 def AppendExtra(self, extra):
1247 self.lines.append(extra)
Tao Baoda30cfa2017-12-01 16:19:46 -08001248
Yifan Hong45433e42019-01-18 13:55:25 -08001249 def __str__(self):
Tao Baoda30cfa2017-12-01 16:19:46 -08001250 return '\n'.join(self.lines)
Yifan Hong45433e42019-01-18 13:55:25 -08001251
1252
1253class MockBlockDifference(object):
Tao Baoda30cfa2017-12-01 16:19:46 -08001254
Yifan Hong45433e42019-01-18 13:55:25 -08001255 def __init__(self, partition, tgt, src=None):
1256 self.partition = partition
1257 self.tgt = tgt
1258 self.src = src
Tao Baoda30cfa2017-12-01 16:19:46 -08001259
Yifan Hong45433e42019-01-18 13:55:25 -08001260 def WriteScript(self, script, _, progress=None,
1261 write_verify_script=False):
1262 if progress:
1263 script.AppendExtra("progress({})".format(progress))
1264 script.AppendExtra("patch({});".format(self.partition))
1265 if write_verify_script:
1266 self.WritePostInstallVerifyScript(script)
Tao Baoda30cfa2017-12-01 16:19:46 -08001267
Yifan Hong45433e42019-01-18 13:55:25 -08001268 def WritePostInstallVerifyScript(self, script):
1269 script.AppendExtra("verify({});".format(self.partition))
1270
1271
1272class FakeSparseImage(object):
Tao Baoda30cfa2017-12-01 16:19:46 -08001273
Yifan Hong45433e42019-01-18 13:55:25 -08001274 def __init__(self, size):
1275 self.blocksize = 4096
1276 self.total_blocks = size // 4096
1277 assert size % 4096 == 0, "{} is not a multiple of 4096".format(size)
1278
1279
1280class DynamicPartitionsDifferenceTest(test_utils.ReleaseToolsTestCase):
Tao Baoda30cfa2017-12-01 16:19:46 -08001281
Yifan Hong45433e42019-01-18 13:55:25 -08001282 @staticmethod
1283 def get_op_list(output_path):
Tao Baof1113e92019-06-18 12:10:14 -07001284 with zipfile.ZipFile(output_path) as output_zip:
Tao Baoda30cfa2017-12-01 16:19:46 -08001285 with output_zip.open('dynamic_partitions_op_list') as op_list:
1286 return [line.decode().strip() for line in op_list.readlines()
1287 if not line.startswith(b'#')]
Yifan Hong45433e42019-01-18 13:55:25 -08001288
1289 def setUp(self):
1290 self.script = MockScriptWriter()
1291 self.output_path = common.MakeTempFile(suffix='.zip')
1292
1293 def test_full(self):
1294 target_info = common.LoadDictionaryFromLines("""
1295dynamic_partition_list=system vendor
1296super_partition_groups=group_foo
1297super_group_foo_group_size={group_size}
1298super_group_foo_partition_list=system vendor
1299""".format(group_size=4 * GiB).split("\n"))
1300 block_diffs = [MockBlockDifference("system", FakeSparseImage(3 * GiB)),
1301 MockBlockDifference("vendor", FakeSparseImage(1 * GiB))]
1302
1303 dp_diff = common.DynamicPartitionsDifference(target_info, block_diffs)
1304 with zipfile.ZipFile(self.output_path, 'w') as output_zip:
1305 dp_diff.WriteScript(self.script, output_zip, write_verify_script=True)
1306
1307 self.assertEqual(str(self.script).strip(), """
1308assert(update_dynamic_partitions(package_extract_file("dynamic_partitions_op_list")));
Yifan Hong45433e42019-01-18 13:55:25 -08001309patch(system);
1310verify(system);
1311unmap_partition("system");
Tao Baof1113e92019-06-18 12:10:14 -07001312patch(vendor);
1313verify(vendor);
1314unmap_partition("vendor");
Yifan Hong45433e42019-01-18 13:55:25 -08001315""".strip())
1316
1317 lines = self.get_op_list(self.output_path)
1318
1319 remove_all_groups = lines.index("remove_all_groups")
1320 add_group = lines.index("add_group group_foo 4294967296")
1321 add_vendor = lines.index("add vendor group_foo")
1322 add_system = lines.index("add system group_foo")
1323 resize_vendor = lines.index("resize vendor 1073741824")
1324 resize_system = lines.index("resize system 3221225472")
1325
1326 self.assertLess(remove_all_groups, add_group,
1327 "Should add groups after removing all groups")
1328 self.assertLess(add_group, min(add_vendor, add_system),
1329 "Should add partitions after adding group")
1330 self.assertLess(add_system, resize_system,
1331 "Should resize system after adding it")
1332 self.assertLess(add_vendor, resize_vendor,
1333 "Should resize vendor after adding it")
1334
1335 def test_inc_groups(self):
1336 source_info = common.LoadDictionaryFromLines("""
1337super_partition_groups=group_foo group_bar group_baz
1338super_group_foo_group_size={group_foo_size}
1339super_group_bar_group_size={group_bar_size}
1340""".format(group_foo_size=4 * GiB, group_bar_size=3 * GiB).split("\n"))
1341 target_info = common.LoadDictionaryFromLines("""
1342super_partition_groups=group_foo group_baz group_qux
1343super_group_foo_group_size={group_foo_size}
1344super_group_baz_group_size={group_baz_size}
1345super_group_qux_group_size={group_qux_size}
1346""".format(group_foo_size=3 * GiB, group_baz_size=4 * GiB,
1347 group_qux_size=1 * GiB).split("\n"))
1348
1349 dp_diff = common.DynamicPartitionsDifference(target_info,
1350 block_diffs=[],
1351 source_info_dict=source_info)
1352 with zipfile.ZipFile(self.output_path, 'w') as output_zip:
1353 dp_diff.WriteScript(self.script, output_zip, write_verify_script=True)
1354
1355 lines = self.get_op_list(self.output_path)
1356
1357 removed = lines.index("remove_group group_bar")
1358 shrunk = lines.index("resize_group group_foo 3221225472")
1359 grown = lines.index("resize_group group_baz 4294967296")
1360 added = lines.index("add_group group_qux 1073741824")
1361
Tao Baof1113e92019-06-18 12:10:14 -07001362 self.assertLess(max(removed, shrunk),
1363 min(grown, added),
Yifan Hong45433e42019-01-18 13:55:25 -08001364 "ops that remove / shrink partitions must precede ops that "
1365 "grow / add partitions")
1366
Yifan Hongbb2658d2019-01-25 12:30:58 -08001367 def test_incremental(self):
Yifan Hong45433e42019-01-18 13:55:25 -08001368 source_info = common.LoadDictionaryFromLines("""
Justin Yun6151e3f2019-06-25 15:58:13 +09001369dynamic_partition_list=system vendor product system_ext
Yifan Hong45433e42019-01-18 13:55:25 -08001370super_partition_groups=group_foo
1371super_group_foo_group_size={group_foo_size}
Justin Yun6151e3f2019-06-25 15:58:13 +09001372super_group_foo_partition_list=system vendor product system_ext
Yifan Hong45433e42019-01-18 13:55:25 -08001373""".format(group_foo_size=4 * GiB).split("\n"))
1374 target_info = common.LoadDictionaryFromLines("""
1375dynamic_partition_list=system vendor product odm
1376super_partition_groups=group_foo group_bar
1377super_group_foo_group_size={group_foo_size}
1378super_group_foo_partition_list=system vendor odm
1379super_group_bar_group_size={group_bar_size}
1380super_group_bar_partition_list=product
1381""".format(group_foo_size=3 * GiB, group_bar_size=1 * GiB).split("\n"))
1382
1383 block_diffs = [MockBlockDifference("system", FakeSparseImage(1536 * MiB),
1384 src=FakeSparseImage(1024 * MiB)),
1385 MockBlockDifference("vendor", FakeSparseImage(512 * MiB),
1386 src=FakeSparseImage(1024 * MiB)),
1387 MockBlockDifference("product", FakeSparseImage(1024 * MiB),
1388 src=FakeSparseImage(1024 * MiB)),
Justin Yun6151e3f2019-06-25 15:58:13 +09001389 MockBlockDifference("system_ext", None,
Yifan Hong45433e42019-01-18 13:55:25 -08001390 src=FakeSparseImage(1024 * MiB)),
1391 MockBlockDifference("odm", FakeSparseImage(1024 * MiB),
1392 src=None)]
1393
1394 dp_diff = common.DynamicPartitionsDifference(target_info, block_diffs,
1395 source_info_dict=source_info)
1396 with zipfile.ZipFile(self.output_path, 'w') as output_zip:
1397 dp_diff.WriteScript(self.script, output_zip, write_verify_script=True)
1398
1399 metadata_idx = self.script.lines.index(
1400 'assert(update_dynamic_partitions(package_extract_file('
1401 '"dynamic_partitions_op_list")));')
1402 self.assertLess(self.script.lines.index('patch(vendor);'), metadata_idx)
1403 self.assertLess(metadata_idx, self.script.lines.index('verify(vendor);'))
1404 for p in ("product", "system", "odm"):
1405 patch_idx = self.script.lines.index("patch({});".format(p))
1406 verify_idx = self.script.lines.index("verify({});".format(p))
1407 self.assertLess(metadata_idx, patch_idx,
1408 "Should patch {} after updating metadata".format(p))
1409 self.assertLess(patch_idx, verify_idx,
1410 "Should verify {} after patching".format(p))
1411
Justin Yun6151e3f2019-06-25 15:58:13 +09001412 self.assertNotIn("patch(system_ext);", self.script.lines)
Yifan Hong45433e42019-01-18 13:55:25 -08001413
1414 lines = self.get_op_list(self.output_path)
1415
Justin Yun6151e3f2019-06-25 15:58:13 +09001416 remove = lines.index("remove system_ext")
Yifan Hong45433e42019-01-18 13:55:25 -08001417 move_product_out = lines.index("move product default")
1418 shrink = lines.index("resize vendor 536870912")
1419 shrink_group = lines.index("resize_group group_foo 3221225472")
1420 add_group_bar = lines.index("add_group group_bar 1073741824")
1421 add_odm = lines.index("add odm group_foo")
1422 grow_existing = lines.index("resize system 1610612736")
1423 grow_added = lines.index("resize odm 1073741824")
1424 move_product_in = lines.index("move product group_bar")
1425
1426 max_idx_move_partition_out_foo = max(remove, move_product_out, shrink)
1427 min_idx_move_partition_in_foo = min(add_odm, grow_existing, grow_added)
1428
1429 self.assertLess(max_idx_move_partition_out_foo, shrink_group,
1430 "Must shrink group after partitions inside group are shrunk"
1431 " / removed")
1432
1433 self.assertLess(add_group_bar, move_product_in,
1434 "Must add partitions to group after group is added")
1435
1436 self.assertLess(max_idx_move_partition_out_foo,
1437 min_idx_move_partition_in_foo,
1438 "Must shrink partitions / remove partitions from group"
1439 "before adding / moving partitions into group")
Yifan Hongbb2658d2019-01-25 12:30:58 -08001440
1441 def test_remove_partition(self):
1442 source_info = common.LoadDictionaryFromLines("""
1443blockimgdiff_versions=3,4
1444use_dynamic_partitions=true
1445dynamic_partition_list=foo
1446super_partition_groups=group_foo
1447super_group_foo_group_size={group_foo_size}
1448super_group_foo_partition_list=foo
1449""".format(group_foo_size=4 * GiB).split("\n"))
1450 target_info = common.LoadDictionaryFromLines("""
1451blockimgdiff_versions=3,4
1452use_dynamic_partitions=true
1453super_partition_groups=group_foo
1454super_group_foo_group_size={group_foo_size}
1455""".format(group_foo_size=4 * GiB).split("\n"))
1456
1457 common.OPTIONS.info_dict = target_info
1458 common.OPTIONS.target_info_dict = target_info
1459 common.OPTIONS.source_info_dict = source_info
1460 common.OPTIONS.cache_size = 4 * 4096
1461
1462 block_diffs = [common.BlockDifference("foo", EmptyImage(),
1463 src=DataImage("source", pad=True))]
1464
1465 dp_diff = common.DynamicPartitionsDifference(target_info, block_diffs,
1466 source_info_dict=source_info)
1467 with zipfile.ZipFile(self.output_path, 'w') as output_zip:
1468 dp_diff.WriteScript(self.script, output_zip, write_verify_script=True)
1469
1470 self.assertNotIn("block_image_update", str(self.script),
Tao Bao2cc0ca12019-03-15 10:44:43 -07001471 "Removed partition should not be patched.")
Yifan Hongbb2658d2019-01-25 12:30:58 -08001472
1473 lines = self.get_op_list(self.output_path)
1474 self.assertEqual(lines, ["remove foo"])