| # -*- coding: utf-8 -*- |
| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Test the image_lib module.""" |
| |
| from __future__ import print_function |
| |
| import collections |
| import gc |
| import glob |
| import os |
| import stat |
| |
| import mock |
| |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_test_lib |
| from chromite.lib import git |
| from chromite.lib import image_lib |
| from chromite.lib import osutils |
| from chromite.lib import retry_util |
| from chromite.lib import partial_mock |
| |
| # pylint: disable=protected-access |
| |
| class FakeException(Exception): |
| """Fake exception used for testing exception handling.""" |
| |
| |
| FAKE_PATH = '/imaginary/file' |
| LOOP_DEV = '/dev/loop9999' |
| LOOP_PART_COUNT = 12 |
| LOOP_PARTITION_INFO = [ |
| image_lib.PartitionInfo( |
| 1, 2928640, 2957311, 28672, 14680064, 'STATE', ''), |
| image_lib.PartitionInfo( |
| 2, 20480, 53247, 32768, 16777216, 'KERN-A', ''), |
| image_lib.PartitionInfo( |
| 3, 286720, 2928639, 2641920, 1352663040, 'ROOT-A', ''), |
| image_lib.PartitionInfo( |
| 4, 53248, 86015, 32768, 16777216, 'KERN-B', ''), |
| image_lib.PartitionInfo( |
| 5, 282624, 286719, 4096, 2097152, 'ROOT-B', ''), |
| image_lib.PartitionInfo( |
| 6, 16448, 16448, 1, 512, 'KERN-C', ''), |
| image_lib.PartitionInfo( |
| 7, 16449, 16449, 1, 512, 'ROOT-C', ''), |
| image_lib.PartitionInfo( |
| 8, 86016, 118783, 32768, 16777216, 'OEM', ''), |
| image_lib.PartitionInfo( |
| 9, 16450, 16450, 1, 512, 'reserved', ''), |
| image_lib.PartitionInfo( |
| 10, 16451, 16451, 1, 512, 'reserved', ''), |
| image_lib.PartitionInfo( |
| 11, 64, 16447, 16384, 8388608, 'RWFW', ''), |
| image_lib.PartitionInfo( |
| 12, 249856, 282623, 32768, 16777216, 'EFI-SYSTEM', ''), |
| ] |
| LOOP_PARTS_DICT = { |
| p.number: '%sp%d' % (LOOP_DEV, p.number) for p in LOOP_PARTITION_INFO} |
| LOOP_PARTS_LIST = LOOP_PARTS_DICT.values() |
| |
| class LoopbackPartitionsMock(image_lib.LoopbackPartitions): |
| """Mocked loopback partition class to use in unit tests.""" |
| # pylint: disable=super-init-not-called |
| def __init__(self, path, destination=None, part_ids=None, mount_opts=None, |
| dev=LOOP_DEV, part_count=0): |
| """Initialize. |
| |
| Args: |
| (shared with LoopbackPartitions) |
| path: Path to the image file. |
| destination: destination directory. |
| part_ids: Mount these partitions at context manager entry. |
| mount_opts: Use these mount_opts for mounting |part_ids|. |
| (unique to LoopbackPartitionsMock) |
| dev: Path for the base loopback device. |
| part_count: How many partition device files to make up. Default: normal |
| partition table. |
| """ |
| self.path = path |
| self.dev = dev |
| self.part_ids = part_ids |
| self.mount_opts = mount_opts |
| if destination: |
| self.destination = destination |
| else: |
| self.destination = osutils.TempDir() |
| if part_count: |
| self._gpt_table = [ |
| image_lib.PartitionInfo(num, 0, 0, 0, '', 'my-%d' % num, '') |
| for num in range(1, part_count + 1)] |
| else: |
| self._gpt_table = LOOP_PARTITION_INFO |
| self.parts = {p.number: '%sp%s' % (dev, p.number) |
| for p in self._gpt_table} |
| self.enable_rw_called = set() |
| self.disable_rw_called = set() |
| # pylint: enable=super-init-not-called |
| |
| def EnableRwMount(self, part_id, offset=0): |
| """Stub out enable rw mount.""" |
| self.enable_rw_called.add((part_id, offset)) |
| |
| def DisableRwMount(self, part_id, offset=0): |
| """Stub out disable rw mount.""" |
| self.disable_rw_called.add((part_id, offset)) |
| |
| def _Mount(self, part, mount_opts): |
| """Stub out mount operations.""" |
| dest_number, _ = self._GetMountPointAndSymlink(part) |
| # Don't actually even try to mount it, let alone mark it mounted. |
| return dest_number |
| |
| def _Unmount(self, part): |
| """Stub out unmount operations.""" |
| |
| def close(self): |
| pass |
| |
| |
| class LoopbackPartitionsTest(cros_test_lib.MockTempDirTestCase): |
| """Test the loopback partitions class""" |
| |
| def setUp(self): |
| self.rc_mock = cros_test_lib.RunCommandMock() |
| self.StartPatcher(self.rc_mock) |
| self.rc_mock.SetDefaultCmdResult() |
| self.rc_mock.AddCmdResult(partial_mock.In('--show'), output=LOOP_DEV) |
| |
| self.PatchObject(image_lib, 'GetImageDiskPartitionInfo', |
| return_value=LOOP_PARTITION_INFO) |
| self.PatchObject(glob, 'glob', return_value=LOOP_PARTS_LIST) |
| self.mount_mock = self.PatchObject(osutils, 'MountDir') |
| self.umount_mock = self.PatchObject(osutils, 'UmountDir') |
| self.retry_mock = self.PatchObject(retry_util, 'RetryException') |
| def fake_which(val, *_arg, **_kwargs): |
| return val |
| self.PatchObject(osutils, 'Which', side_effect=fake_which) |
| |
| def testContextManager(self): |
| """Test using the loopback class as a context manager.""" |
| with image_lib.LoopbackPartitions(FAKE_PATH) as lb: |
| self.rc_mock.assertCommandContains(['losetup', '--show', '-f', FAKE_PATH]) |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['partx', '-a', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV], |
| expected=False) |
| self.assertEqual(lb.parts, LOOP_PARTS_DICT) |
| self.assertEqual(lb._gpt_table, LOOP_PARTITION_INFO) |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV]) |
| |
| def testContextManagerWithMounts(self): |
| """Test using the loopback class as a context manager with mounts.""" |
| syml = self.PatchObject(osutils, 'SafeSymlink') |
| part_ids = (1, 'ROOT-A') |
| with image_lib.LoopbackPartitions( |
| FAKE_PATH, part_ids=part_ids, mount_opts=('ro',)) as lb: |
| expected_mounts = set() |
| expected_calls = [] |
| for part_id in part_ids: |
| for part in LOOP_PARTITION_INFO: |
| if part.name == part_id or part.number == part_id: |
| expected_mounts.add(part) |
| expected_calls.append( |
| mock.call('dir-%d' % part.number, os.path.join( |
| lb.destination, 'dir-%s' % part.name))) |
| break |
| self.rc_mock.assertCommandContains(['losetup', '--show', '-f', FAKE_PATH]) |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['partx', '-a', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV], |
| expected=False) |
| self.assertEqual(lb.parts, LOOP_PARTS_DICT) |
| self.assertEqual(lb._gpt_table, LOOP_PARTITION_INFO) |
| self.assertEqual(expected_calls, syml.call_args_list) |
| self.assertEqual(expected_mounts, lb._mounted) |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV]) |
| |
| |
| def testManual(self): |
| """Test using the loopback class closed manually.""" |
| lb = image_lib.LoopbackPartitions(FAKE_PATH) |
| self.rc_mock.assertCommandContains(['losetup', '--show', '-f', FAKE_PATH]) |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['partx', '-a', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV], |
| expected=False) |
| self.assertEqual(lb.parts, LOOP_PARTS_DICT) |
| self.assertEqual(lb._gpt_table, LOOP_PARTITION_INFO) |
| lb.close() |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV]) |
| |
| def gcFunc(self): |
| """This function isolates a local variable so it'll be garbage collected.""" |
| lb = image_lib.LoopbackPartitions(FAKE_PATH) |
| self.rc_mock.assertCommandContains(['losetup', '--show', '-f', FAKE_PATH]) |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['partx', '-a', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV], |
| expected=False) |
| self.assertEqual(lb.parts, LOOP_PARTS_DICT) |
| self.assertEqual(lb._gpt_table, LOOP_PARTITION_INFO) |
| |
| def testGarbageCollected(self): |
| """Test using the loopback class closed by garbage collection.""" |
| self.gcFunc() |
| # Force garbage collection in case python didn't already clean up the |
| # loopback object. |
| gc.collect() |
| self.rc_mock.assertCommandContains(['partx', '-d', LOOP_DEV]) |
| self.rc_mock.assertCommandContains(['losetup', '--detach', LOOP_DEV]) |
| |
| def testMountUnmount(self): |
| """Test Mount() and Unmount() entry points.""" |
| lb = image_lib.LoopbackPartitions(FAKE_PATH, destination=self.tempdir) |
| # Mount four partitions. |
| lb.Mount((1, 3, 'ROOT-B', 'ROOT-C')) |
| for p in (1, 3, 5, 7): |
| self.mount_mock.assert_any_call( |
| '%sp%d' % (LOOP_DEV, p), '%s/dir-%d' % (self.tempdir, p), |
| makedirs=True, skip_mtab=False, sudo=True, mount_opts=('ro',)) |
| linkname = '%s/dir-%s' % (self.tempdir, LOOP_PARTITION_INFO[p - 1].name) |
| self.assertTrue(stat.S_ISLNK(os.lstat(linkname).st_mode)) |
| self.assertEqual(4, self.mount_mock.call_count) |
| self.umount_mock.assert_not_called() |
| |
| # Unmount half of them, confirm that they were unmounted. |
| lb.Unmount((1, 'ROOT-B')) |
| for p in (1, 5): |
| self.umount_mock.assert_any_call('%s/dir-%d' % (self.tempdir, p), |
| cleanup=False) |
| self.assertEqual(2, self.umount_mock.call_count) |
| self.umount_mock.reset_mock() |
| |
| # Close the object, so that we unmount the other half of them. |
| lb.close() |
| for p in (3, 7): |
| self.umount_mock.assert_any_call('%s/dir-%d' % (self.tempdir, p), |
| cleanup=False) |
| self.assertEqual(2, self.umount_mock.call_count) |
| |
| # Verify that the directories were cleaned up. |
| for p in (1, 3): |
| self.retry_mock.assert_any_call( |
| cros_build_lib.RunCommandError, 60, osutils.RmDir, |
| '%s/dir-%d' % (self.tempdir, p), sudo=True, sleep=1) |
| |
| def testMountingMountedPartReturnsName(self): |
| """Test that Mount returns the directory name even when already mounted.""" |
| lb = image_lib.LoopbackPartitions(FAKE_PATH, destination=self.tempdir) |
| dirname = '%s/dir-%d' % (self.tempdir, lb._gpt_table[0].number) |
| # First make sure we get the directory name when we actually mount. |
| self.assertEqual(dirname, lb._Mount(lb._gpt_table[0], ('ro',))) |
| # Then make sure we get it when we call it again. |
| self.assertEqual(dirname, lb._Mount(lb._gpt_table[0], ('ro',))) |
| lb.close() |
| |
| def testRemountCallsMount(self): |
| """Test that Mount returns the directory name even when already mounted.""" |
| lb = image_lib.LoopbackPartitions(FAKE_PATH, destination=self.tempdir) |
| devname = '%sp%d' % (LOOP_DEV, lb._gpt_table[0].number) |
| dirname = '%s/dir-%d' % (self.tempdir, lb._gpt_table[0].number) |
| # First make sure we get the directory name when we actually mount. |
| self.assertEqual(dirname, lb._Mount(lb._gpt_table[0], ('ro',))) |
| self.mount_mock.assert_called_once_with( |
| devname, dirname, |
| makedirs=True, skip_mtab=False, sudo=True, mount_opts=('ro',)) |
| # Then make sure we get it when we call it again. |
| self.assertEqual(dirname, lb._Mount(lb._gpt_table[0], ('remount', 'rw'))) |
| self.assertEqual( |
| mock.call(devname, dirname, makedirs=True, skip_mtab=False, |
| sudo=True, mount_opts=('remount', 'rw')), |
| self.mount_mock.call_args) |
| lb.close() |
| |
| def testGetPartitionDevName(self): |
| """Test GetPartitionDevName().""" |
| lb = image_lib.LoopbackPartitions(FAKE_PATH) |
| for part in LOOP_PARTITION_INFO: |
| self.assertEqual('%sp%d' % (LOOP_DEV, part.number), |
| lb.GetPartitionDevName(part.number)) |
| if part.name != 'reserved': |
| self.assertEqual('%sp%d' % (LOOP_DEV, part.number), |
| lb.GetPartitionDevName(part.name)) |
| lb.close() |
| |
| def test_GetMountPointAndSymlink(self): |
| """Test _GetMountPointAndSymlink().""" |
| lb = image_lib.LoopbackPartitions(FAKE_PATH, destination=self.tempdir) |
| for part in LOOP_PARTITION_INFO: |
| expected = [os.path.join(lb.destination, 'dir-%s' % n) |
| for n in (part.number, part.name)] |
| self.assertEqual(expected, list(lb._GetMountPointAndSymlink(part))) |
| lb.close() |
| |
| def testIsExt2OnVarious(self): |
| """Test _IsExt2 works with the various partition types.""" |
| FS_PARTITIONS = (1, 3, 8) |
| # STATE, ROOT-A, and OEM generally have ext2 filesystems. |
| for x in FS_PARTITIONS: |
| self.rc_mock.AddCmdResult( |
| partial_mock.In('if=%sp%d' % (LOOP_DEV, x)), |
| output=b'\x53\xef') |
| # Throw errors on all of the partitions that are < 1000 bytes. |
| for part in LOOP_PARTITION_INFO: |
| if part.size < 1000: |
| self.rc_mock.AddCmdResult( |
| partial_mock.In('if=%sp%d' % (LOOP_DEV, part.number)), |
| returncode=1, error='Seek failed\n') |
| lb = image_lib.LoopbackPartitions(FAKE_PATH, destination=self.tempdir) |
| # We expect that only the partitions in FS_PARTITIONS are ext2. |
| self.assertEqual( |
| [part.number in FS_PARTITIONS for part in LOOP_PARTITION_INFO], |
| [lb._IsExt2(part.name) for part in LOOP_PARTITION_INFO]) |
| lb.close() |
| |
| |
| class LsbUtilsTest(cros_test_lib.MockTempDirTestCase): |
| """Tests the various LSB utilities.""" |
| |
| def setUp(self): |
| # Patch os.getuid(..) to pretend running as root, so reading/writing the |
| # lsb-release file doesn't require escalated privileges and the test can |
| # clean itself up correctly. |
| self.PatchObject(os, 'getuid', return_value=0) |
| |
| def testWriteLsbRelease(self): |
| """Tests writing out the lsb_release file using WriteLsbRelease(..).""" |
| rc_mock = self.PatchObject(cros_build_lib, 'sudo_run') |
| fields = collections.OrderedDict(( |
| ('x', '1'), ('y', '2'), ('foo', 'bar'), |
| )) |
| image_lib.WriteLsbRelease(self.tempdir, fields) |
| lsb_release_file = os.path.join(self.tempdir, 'etc', 'lsb-release') |
| expected_content = 'x=1\ny=2\nfoo=bar\n' |
| self.assertFileContents(lsb_release_file, expected_content) |
| rc_mock.assert_called_once_with([ |
| 'setfattr', '-n', 'security.selinux', '-v', |
| 'u:object_r:cros_conf_file:s0', |
| os.path.join(self.tempdir, 'etc/lsb-release')]) |
| |
| # Test that WriteLsbRelease(..) correctly handles an existing file. |
| rc_mock = self.PatchObject(cros_build_lib, 'sudo_run') |
| fields = collections.OrderedDict(( |
| ('newkey1', 'value1'), ('newkey2', 'value2'), ('a', '3'), ('b', '4'), |
| )) |
| image_lib.WriteLsbRelease(self.tempdir, fields) |
| expected_content = ('x=1\ny=2\nfoo=bar\nnewkey1=value1\nnewkey2=value2\n' |
| 'a=3\nb=4\n') |
| self.assertFileContents(lsb_release_file, expected_content) |
| rc_mock.assert_called_once_with([ |
| 'setfattr', '-n', 'security.selinux', '-v', |
| 'u:object_r:cros_conf_file:s0', |
| os.path.join(self.tempdir, 'etc/lsb-release')]) |
| |
| |
| class BuildImagePathTest(cros_test_lib.MockTempDirTestCase): |
| """BuildImagePath tests.""" |
| |
| def setUp(self): |
| self.board = 'board' |
| self.board_dir = os.path.join(self.tempdir, self.board) |
| |
| D = cros_test_lib.Directory |
| filesystem = ( |
| D(self.board, ('recovery_image.bin', 'other_image.bin')), |
| 'full_path_image.bin', |
| ) |
| cros_test_lib.CreateOnDiskHierarchy(self.tempdir, filesystem) |
| |
| self.full_path = os.path.join(self.tempdir, 'full_path_image.bin') |
| |
| def testBuildImagePath(self): |
| """BuildImagePath tests.""" |
| self.PatchObject(image_lib, 'GetLatestImageLink', |
| return_value=os.path.join(self.tempdir, self.board)) |
| |
| # Board and full image path provided. |
| result = image_lib.BuildImagePath(self.board, self.full_path) |
| self.assertEqual(self.full_path, result) |
| |
| # Only full image path provided. |
| result = image_lib.BuildImagePath(None, self.full_path) |
| self.assertEqual(self.full_path, result) |
| |
| # Full image path provided that does not exist. |
| with self.assertRaises(image_lib.ImageDoesNotExistError): |
| image_lib.BuildImagePath(self.board, '/does/not/exist') |
| with self.assertRaises(image_lib.ImageDoesNotExistError): |
| image_lib.BuildImagePath(None, '/does/not/exist') |
| |
| # Default image is used. |
| result = image_lib.BuildImagePath(self.board, None) |
| self.assertEqual(os.path.join(self.board_dir, 'recovery_image.bin'), result) |
| |
| # Image basename provided. |
| result = image_lib.BuildImagePath(self.board, 'other_image.bin') |
| self.assertEqual(os.path.join(self.board_dir, 'other_image.bin'), result) |
| |
| # Image basename provided that does not exist. |
| with self.assertRaises(image_lib.ImageDoesNotExistError): |
| image_lib.BuildImagePath(self.board, 'does_not_exist.bin') |
| |
| default_mock = self.PatchObject(cros_build_lib, 'GetDefaultBoard') |
| |
| # Nothing provided, and no default. |
| default_mock.return_value = None |
| with self.assertRaises(image_lib.ImageDoesNotExistError): |
| image_lib.BuildImagePath(None, None) |
| |
| # Nothing provided, with default. |
| default_mock.return_value = 'board' |
| result = image_lib.BuildImagePath(None, None) |
| self.assertEqual(os.path.join(self.board_dir, 'recovery_image.bin'), result) |
| |
| |
| class SecurityTestConfigTest(cros_test_lib.RunCommandTempDirTestCase): |
| """SecurityTestConfig class tests.""" |
| |
| # pylint: disable=protected-access |
| |
| def setUp(self): |
| self.image = '/path/to/image.bin' |
| self.baselines = '/path/to/baselines' |
| self.vboot_hash = 'abc123' |
| self.config = image_lib.SecurityTestConfig(self.image, self.baselines, |
| self.vboot_hash, self.tempdir) |
| |
| def testVbootCheckout(self): |
| """Test normal flow - clone and checkout.""" |
| clone_patch = self.PatchObject(git, 'Clone') |
| self.config._VbootCheckout() |
| clone_patch.assert_called_once() |
| self.assertCommandContains(['git', 'checkout', self.vboot_hash]) |
| |
| # Make sure it doesn't try to clone & checkout again after already having |
| # done so successfully. |
| clone_patch = self.PatchObject(git, 'Clone') |
| self.config._VbootCheckout() |
| clone_patch.assert_not_called() |
| |
| def testVbootCheckoutError(self): |
| """Test exceptions in a git command.""" |
| rce = cros_build_lib.RunCommandError('error') |
| self.PatchObject(git, 'Clone', side_effect=rce) |
| with self.assertRaises(image_lib.VbootCheckoutError): |
| self.config._VbootCheckout() |
| |
| def testVbootCheckoutNoDirectory(self): |
| """Test the error handling when the directory does not exist.""" |
| # Test directory that does not exist. |
| self.config.directory = '/DOES/NOT/EXIST' |
| with self.assertRaises(image_lib.SecurityConfigDirectoryError): |
| self.config._VbootCheckout() |
| |
| def testRunCheck(self): |
| """RunCheck tests.""" |
| # No config argument when running check. |
| self.config.RunCheck('check1', False) |
| check1 = os.path.join(self.config._checks_dir, 'ensure_check1.sh') |
| config1 = os.path.join(self.baselines, 'ensure_check1.config') |
| self.assertCommandContains([check1, self.image]) |
| self.assertCommandContains([config1], expected=False) |
| |
| # Include config argument when running check. |
| self.config.RunCheck('check2', True) |
| check2 = os.path.join(self.config._checks_dir, 'ensure_check2.sh') |
| config2 = os.path.join(self.baselines, 'ensure_check2.config') |
| self.assertCommandContains([check2, self.image, config2]) |
| |
| |
| class GetImageDiskPartitionInfoTests(cros_test_lib.RunCommandTestCase): |
| """Tests the GetImageDiskPartitionInfo function.""" |
| |
| SAMPLE_PARTED = """/foo/chromiumos_qemu_image.bin:\ |
| 2271240192B:file:512:512:gpt::; |
| 11:32768B:8421375B:8388608B::RWFW:; |
| 6:8421376B:8421887B:512B::KERN-C:; |
| 7:8421888B:8422399B:512B::ROOT-C:; |
| 9:8422400B:8422911B:512B::reserved:; |
| 10:8422912B:8423423B:512B::reserved:; |
| 2:10485760B:27262975B:16777216B::KERN-A:; |
| 4:27262976B:44040191B:16777216B::KERN-B:; |
| 8:44040192B:60817407B:16777216B:ext4:OEM:msftdata; |
| 12:127926272B:161480703B:33554432B:fat16:EFI-SYSTEM:boot, esp; |
| 5:161480704B:163577855B:2097152B::ROOT-B:; |
| 3:163577856B:2260729855B:2097152000B:ext2:ROOT-A:; |
| 1:2260729856B:2271215615B:10485760B:ext2:STATE:msftdata; |
| """ |
| |
| SAMPLE_CGPT = """ |
| start size part contents |
| 0 1 PMBR (Boot GUID: 88FB7EB8-2B3F-B943-B933-\ |
| EEC571FFB6E1) |
| 1 1 Pri GPT header |
| 2 32 Pri GPT table |
| 1921024 2097152 1 Label: "STATE" |
| Type: Linux data |
| UUID: EEBD83BE-397E-BD44-878B-0DDDD5A5C510 |
| 20480 32768 2 Label: "KERN-A" |
| Type: ChromeOS kernel |
| UUID: 7007C2F3-08E5-AB40-A4BC-FF5B01F5460D |
| Attr: priority=15 tries=15 successful=1 |
| 1101824 819200 3 Label: "ROOT-A" |
| Type: ChromeOS rootfs |
| UUID: F4C5C3AD-027F-894B-80CD-3DEC57932948 |
| 53248 32768 4 Label: "KERN-B" |
| Type: ChromeOS kernel |
| UUID: C85FB478-404C-8741-ADB8-11312A35880D |
| Attr: priority=0 tries=0 successful=0 |
| 282624 819200 5 Label: "ROOT-B" |
| Type: ChromeOS rootfs |
| UUID: A99F4231-1EC3-C542-AC0C-DF3729F5DB07 |
| 16448 1 6 Label: "KERN-C" |
| Type: ChromeOS kernel |
| UUID: 81F0E336-FAC9-174D-A08C-864FE627B637 |
| Attr: priority=0 tries=0 successful=0 |
| 16449 1 7 Label: "ROOT-C" |
| Type: ChromeOS rootfs |
| UUID: 9E127FCA-30C1-044E-A5F2-DF74E6932692 |
| 86016 32768 8 Label: "OEM" |
| Type: Linux data |
| UUID: 72986347-A37C-684F-9A19-4DBAF41C55A9 |
| 16450 1 9 Label: "reserved" |
| Type: ChromeOS reserved |
| UUID: BA85A0A7-1850-964D-8EF8-6707AC106C3A |
| 16451 1 10 Label: "reserved" |
| Type: ChromeOS reserved |
| UUID: 16C9EC9B-50FA-DD46-98DC-F781360817B4 |
| 64 16384 11 Label: "RWFW" |
| Type: ChromeOS firmware |
| UUID: BE8AECB9-4F78-7C44-8F23-5A9273B7EC8F |
| 249856 32768 12 Label: "EFI-SYSTEM" |
| Type: EFI System Partition |
| UUID: 88FB7EB8-2B3F-B943-B933-EEC571FFB6E1 |
| 4050847 32 Sec GPT table |
| 4050879 1 Sec GPT header |
| """ |
| |
| def testCgpt(self): |
| """Tests that we can list all partitions with `cgpt` correctly.""" |
| self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True) |
| self.rc.AddCmdResult(partial_mock.Ignore(), output=self.SAMPLE_CGPT) |
| partitions = image_lib.GetImageDiskPartitionInfo('...') |
| part_dict = {p.name: p for p in partitions} |
| self.assertEqual(part_dict['STATE'].start, 983564288) |
| self.assertEqual(part_dict['STATE'].size, 1073741824) |
| self.assertEqual(part_dict['STATE'].number, 1) |
| self.assertEqual(part_dict['STATE'].name, 'STATE') |
| self.assertEqual(part_dict['EFI-SYSTEM'].start, 249856 * 512) |
| self.assertEqual(part_dict['EFI-SYSTEM'].size, 32768 * 512) |
| self.assertEqual(part_dict['EFI-SYSTEM'].number, 12) |
| self.assertEqual(part_dict['EFI-SYSTEM'].name, 'EFI-SYSTEM') |
| self.assertEqual(12, len(partitions)) |
| |
| def testNormalPath(self): |
| self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False) |
| self.rc.AddCmdResult(partial_mock.Ignore(), output=self.SAMPLE_PARTED) |
| partitions = image_lib.GetImageDiskPartitionInfo('_ignored') |
| part_dict = {p.name: p for p in partitions} |
| self.assertEqual(12, len(partitions)) |
| self.assertEqual(1, part_dict['STATE'].number) |
| self.assertEqual(2097152000, part_dict['ROOT-A'].size) |
| |
| def testKeyedByNumber(self): |
| self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False) |
| self.rc.AddCmdResult(partial_mock.Ignore(), output=self.SAMPLE_PARTED) |
| partitions = image_lib.GetImageDiskPartitionInfo( |
| '_ignored' |
| ) |
| part_dict = {p.number: p for p in partitions} |
| self.assertEqual(12, len(part_dict)) |
| self.assertEqual('STATE', part_dict[1].name) |
| self.assertEqual(2097152000, part_dict[3].size) |
| self.assertEqual('reserved', part_dict[9].name) |
| self.assertEqual('reserved', part_dict[10].name) |
| |
| def testChangeUnitInsideChroot(self): |
| self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True) |
| self.rc.AddCmdResult(partial_mock.Ignore(), output=self.SAMPLE_CGPT) |
| partitions = image_lib.GetImageDiskPartitionInfo('_ignored') |
| part_dict = {p.name: p for p in partitions} |
| self.assertEqual(part_dict['STATE'].start, 983564288) |
| self.assertEqual(part_dict['STATE'].size, 1073741824) |