| #!/usr/bin/python |
| # pylint: disable=W0212 |
| # |
| # Copyright (c) 2012 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. |
| |
| """Unit tests for gooftool module.""" |
| |
| import logging |
| import mox |
| import os |
| import unittest |
| |
| from collections import namedtuple |
| from contextlib import contextmanager |
| from tempfile import NamedTemporaryFile |
| |
| import factory_common # pylint: disable=W0611 |
| from cros.factory import gooftool |
| from cros.factory.common import Error |
| from cros.factory.common import Shell |
| from cros.factory.gooftool import crosfw |
| from cros.factory.gooftool import Gooftool |
| from cros.factory.gooftool.bmpblk import unpack_bmpblock |
| from cros.factory.gooftool.probe import Probe, ReadRoVpd |
| from cros.factory.hwdb import hwid_tool |
| from cros.factory.hwdb.hwid_tool import ProbeResults # pylint: disable=E0611 |
| from cros.factory.gooftool import Mismatch |
| from cros.factory.gooftool import ProbedComponentResult |
| from cros.factory.system import vpd |
| from cros.factory.test import branding |
| from cros.factory.utils import file_utils |
| from cros.factory.utils.process_utils import CheckOutput |
| |
| _TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), 'testdata') |
| |
| # A stub for stdout |
| StubStdout = namedtuple('StubStdout', ['stdout']) |
| |
| class MockMainFirmware(object): |
| """Mock main firmware object.""" |
| def __init__(self, image=None): |
| self.GetFileName = lambda: "firmware" |
| self.Write = lambda sections: sections == ['GBB'] |
| self.GetFirmwareImage = lambda: image |
| |
| class MockFirmwareImage(object): |
| """Mock firmware image object.""" |
| def __init__(self, section_map): |
| self.has_section = lambda name: name in section_map |
| self.get_section = lambda name: section_map[name] |
| |
| class MockFile(object): |
| """Mock file object.""" |
| def __init__(self): |
| self.name = 'filename' |
| self.read = lambda: "read_results" |
| def __enter__(self): |
| return self |
| def __exit__(self, filetype, value, traceback): |
| pass |
| |
| class UtilTest(unittest.TestCase): |
| """Unit test for Util.""" |
| def setUp(self): |
| self.mox = mox.Mox() |
| |
| self._util = gooftool.Util() |
| |
| # Mock out small wrapper functions that do not need unittests. |
| self._util.shell = self.mox.CreateMock(Shell) |
| self.mox.StubOutWithMock(self._util, "_IsDeviceFixed") |
| self.mox.StubOutWithMock(self._util, "FindScript") |
| |
| def tearDown(self): |
| self.mox.VerifyAll() |
| self.mox.UnsetStubs() |
| |
| def testGetPrimaryDevicePath(self): |
| '''Test for GetPrimaryDevice.''' |
| |
| self._util._IsDeviceFixed( |
| "sda").MultipleTimes().AndReturn(True) |
| self._util._IsDeviceFixed( |
| "sdb").MultipleTimes().AndReturn(False) |
| |
| self._util.shell('cgpt find -t rootfs').MultipleTimes().AndReturn( |
| StubStdout("/dev/sda3\n/dev/sda1\n/dev/sdb1")) |
| |
| self.mox.ReplayAll() |
| |
| self.assertEquals("/dev/sda", self._util.GetPrimaryDevicePath()) |
| self.assertEquals("/dev/sda1", self._util.GetPrimaryDevicePath(1)) |
| self.assertEquals("/dev/sda2", self._util.GetPrimaryDevicePath(2)) |
| |
| # also test thin callers |
| self.assertEquals("/dev/sda5", self._util.GetReleaseRootPartitionPath()) |
| self.assertEquals("/dev/sda4", self._util.GetReleaseKernelPartitionPath()) |
| |
| def testGetPrimaryDevicePathMultiple(self): |
| '''Test for GetPrimaryDevice when multiple primary devices are found.''' |
| |
| self._util._IsDeviceFixed( |
| "sda").MultipleTimes().AndReturn(True) |
| self._util._IsDeviceFixed( |
| "sdb").MultipleTimes().AndReturn(True) |
| |
| self._util.shell('cgpt find -t rootfs').AndReturn( |
| StubStdout("/dev/sda3\n/dev/sda1\n/dev/sdb1")) |
| |
| self.mox.ReplayAll() |
| |
| self.assertRaises(Error, self._util.GetPrimaryDevicePath) |
| |
| def testFindRunScript(self): |
| self._util.FindScript(mox.IsA(str)).MultipleTimes().AndReturn("script") |
| |
| stub_result = lambda: None |
| stub_result.success = True |
| self._util.shell("script").AndReturn(stub_result) # option = [] |
| self._util.shell("script").AndReturn(stub_result) # option = None |
| self._util.shell("script a").AndReturn(stub_result) |
| self._util.shell("script a b").AndReturn(stub_result) |
| self._util.shell("c=d script a b").AndReturn(stub_result) |
| self._util.shell("c=d script").AndReturn(stub_result) |
| |
| self.mox.ReplayAll() |
| |
| self._util.FindAndRunScript("script") |
| self._util.FindAndRunScript("script", None) |
| self._util.FindAndRunScript("script", ["a"]) |
| self._util.FindAndRunScript("script", ["a", "b"]) |
| self._util.FindAndRunScript("script", ["a", "b"], ["c=d"]) |
| self._util.FindAndRunScript("script", None, ["c=d"]) |
| |
| def testGetCrosSystem(self): |
| self._util.shell('crossystem').AndReturn(StubStdout( |
| 'first_flag = 123 # fake comment\n' |
| 'second_flag = flag_2_value # another fake comment')) |
| |
| self.mox.ReplayAll() |
| self.assertEqual({'first_flag': '123', 'second_flag': 'flag_2_value'}, |
| self._util.GetCrosSystem()) |
| |
| |
| class GooftoolTest(unittest.TestCase): |
| """Unit test for Gooftool.""" |
| def setUp(self): |
| self.mox = mox.Mox() |
| |
| # Probe should always be mocked in the unit test since this test is not |
| # likely to be ran on a DUT. |
| self._mock_probe = self.mox.CreateMock(Probe) |
| test_db = hwid_tool.HardwareDb(_TEST_DATA_PATH) |
| |
| self._gooftool = Gooftool(probe=self._mock_probe, hardware_db=test_db) |
| self._gooftool._util = self.mox.CreateMock(gooftool.Util) |
| self._gooftool._util.shell = self.mox.CreateMock(Shell) |
| |
| self._gooftool._crosfw = self.mox.CreateMock(crosfw) |
| self._gooftool._unpack_bmpblock = self.mox.CreateMock(unpack_bmpblock) |
| self._gooftool._read_ro_vpd = self.mox.CreateMock(ReadRoVpd) |
| self._gooftool._named_temporary_file = self.mox.CreateMock( |
| NamedTemporaryFile) |
| |
| gooftool.CheckOutput = self.mox.CreateMock(CheckOutput) |
| |
| def tearDown(self): |
| self.mox.VerifyAll() |
| self.mox.UnsetStubs() |
| |
| def testVerifyComponents(self): |
| '''Test if the Gooftool.VerifyComponent() works properly. |
| |
| This test tries to probe three components [camera, battery, cpu], where |
| 'camera' returns a valid result. |
| 'battery' returns a false result. |
| 'cpu' does not return any result. |
| 'tpm' returns multiple results. |
| ''' |
| |
| self._mock_probe( |
| probe_initial_config=False, |
| probe_volatile=False, |
| target_comp_classes=['camera', 'battery', 'cpu', 'tpm']).AndReturn( |
| ProbeResults( |
| found_probe_value_map={ |
| 'camera': 'CAMERA_1', |
| 'battery': 'fake value', |
| 'tpm': ['TPM_1', 'TPM_2', 'fake value']}, |
| missing_component_classes={}, |
| found_volatile_values=[], |
| initial_configs={})) |
| |
| self.mox.ReplayAll() |
| |
| self.assertEquals( |
| {'camera': [('camera_1', 'CAMERA_1', None)], |
| 'battery': [(None, 'fake value', mox.IsA(str))], |
| 'cpu': [(None, None, mox.IsA(str))], |
| 'tpm': [('tpm_1', 'TPM_1', None), |
| ('tpm_2', 'TPM_2', None), |
| (None, 'fake value', mox.IsA(str))]}, |
| self._gooftool.VerifyComponents(['camera', 'battery', 'cpu', 'tpm'])) |
| |
| def testVerifyBadComponents(self): |
| self.mox.ReplayAll() |
| |
| self.assertRaises(ValueError, self._gooftool.VerifyComponents, []) |
| self.assertRaises(ValueError, |
| self._gooftool.VerifyComponents, ['bad_class_name']) |
| self.assertRaises( |
| ValueError, |
| self._gooftool.VerifyComponents, ['camera', 'bad_class_name']) |
| |
| def testFindBOMMismatches(self): |
| self.mox.ReplayAll() |
| |
| # expect fully matched result |
| self.assertEquals( |
| {}, |
| self._gooftool.FindBOMMismatches( |
| 'BENDER', |
| 'LEELA', |
| {'camera': [ProbedComponentResult('camera_1', 'CAMERA_1', None)], |
| 'tpm': [ProbedComponentResult('tpm_1', 'TPM_1', None)], |
| 'vga': [ProbedComponentResult('vga_1', 'VGA_1', None)]})) |
| |
| # expect mismatch results |
| self.assertEquals( |
| {'camera': Mismatch( |
| expected=set(['camera_1']), actual=set(['camera_2'])), |
| 'vga': Mismatch( |
| expected=set(['vga_1']), actual=set(['vga_2']))}, |
| self._gooftool.FindBOMMismatches( |
| 'BENDER', |
| 'LEELA', |
| {'camera': [ProbedComponentResult('camera_2', 'CAMERA_2', None)], |
| 'tpm': [ProbedComponentResult('tpm_1', 'TPM_1', None)], |
| 'vga': [ProbedComponentResult('vga_2', 'VGA_2', None)]})) |
| |
| def testFindBOMMismatchesMissingDontcare(self): |
| self.mox.ReplayAll() |
| |
| # expect fully matched result |
| self.assertEquals( |
| {}, |
| self._gooftool.FindBOMMismatches( |
| 'BENDER', |
| 'FRY', |
| # expect = don't care, actual = some value |
| {'camera': [ProbedComponentResult('camera_2', 'CAMERA_2', None)], |
| # expect = don't care, actual = missing |
| 'cpu': [ProbedComponentResult(None, None, "Missing")], |
| # expect = missing, actual = missing |
| 'cellular': [ProbedComponentResult(None, None, "Missing")]})) |
| |
| # expect mismatch results |
| self.assertEquals( |
| {'cellular': Mismatch( |
| expected=None, |
| actual=[ProbedComponentResult('cellular_1', 'CELLULAR_1', None)]), |
| 'dram': Mismatch( |
| expected=set(['dram_1']), actual=set([None]))}, |
| self._gooftool.FindBOMMismatches( |
| 'BENDER', |
| 'FRY', |
| # expect correct value |
| {'camera': [ProbedComponentResult('camera_2', 'CAMERA_2', None)], |
| # expect = missing, actual = some value |
| 'cellular': [ProbedComponentResult( |
| 'cellular_1', 'CELLULAR_1', None)], |
| # expect = some value, actual = missing |
| 'dram': [ProbedComponentResult(None, None, 'Missing')]})) |
| |
| def testFindBOMMismatchesError(self): |
| self.mox.ReplayAll() |
| |
| self.assertRaises( |
| ValueError, self._gooftool.FindBOMMismatches, 'NO_BARD', 'LEELA', |
| {'camera': [ProbedComponentResult('camera_1', 'CAMERA_1', None)]}) |
| self.assertRaises( |
| ValueError, self._gooftool.FindBOMMismatches, 'BENDER', 'NO_BOM', {}) |
| self.assertRaises( |
| ValueError, self._gooftool.FindBOMMismatches, 'BENDER', None, {}) |
| self.assertRaises( |
| ValueError, self._gooftool.FindBOMMismatches, 'BENDER', 'LEELA', None) |
| |
| def testVerifyKey(self): |
| self._gooftool._util.GetReleaseKernelPartitionPath().AndReturn("kernel") |
| |
| self._gooftool._crosfw.LoadMainFirmware().AndReturn(MockMainFirmware()) |
| |
| self._gooftool._util.FindAndRunScript("verify_keys.sh", |
| ["kernel", "firmware"]) |
| |
| self.mox.ReplayAll() |
| self._gooftool.VerifyKeys() |
| |
| def testVerifySystemTime(self): |
| self._gooftool._util.GetReleaseRootPartitionPath().AndReturn("root") |
| |
| self._gooftool._util.FindAndRunScript("verify_system_time.sh", ["root"]) |
| |
| self.mox.ReplayAll() |
| self._gooftool.VerifySystemTime() |
| |
| def testVerifyRootFs(self): |
| self._gooftool._util.GetReleaseRootPartitionPath().AndReturn("root") |
| |
| self._gooftool._util.FindAndRunScript("verify_rootfs.sh", ["root"]) |
| |
| self.mox.ReplayAll() |
| self._gooftool.VerifyRootFs() |
| |
| def testVerifyTPM(self): |
| gooftool.CheckOutput( |
| ['cryptohome', '--action=tpm_status']).AndReturn( |
| '''TPM Enabled: true |
| TPM Owned: false |
| TPM Being Owned: false |
| TPM Ready: false |
| TPM Password:''') |
| self.mox.ReplayAll() |
| self._gooftool.VerifyTPM() |
| |
| def testVerifyManagementEngineLocked(self): |
| data_no_me = {'RO_SECTION': ''} |
| data_me_locked = {'SI_ME': chr(0xff) * 1024} |
| data_me_unlocked = {'SI_ME': chr(0x55) * 1024} |
| self._gooftool._crosfw.LoadMainFirmware().AndReturn( |
| MockMainFirmware(MockFirmwareImage(data_no_me))) |
| self._gooftool._crosfw.LoadMainFirmware().AndReturn( |
| MockMainFirmware(MockFirmwareImage(data_me_locked))) |
| self._gooftool._crosfw.LoadMainFirmware().AndReturn( |
| MockMainFirmware(MockFirmwareImage(data_me_unlocked))) |
| self.mox.ReplayAll() |
| self._gooftool.VerifyManagementEngineLocked() |
| self._gooftool.VerifyManagementEngineLocked() |
| self.assertRaises(Error, self._gooftool.VerifyManagementEngineLocked) |
| |
| def testClearGBBFlags(self): |
| self._gooftool._util.FindAndRunScript("clear_gbb_flags.sh") |
| self.mox.ReplayAll() |
| self._gooftool.ClearGBBFlags() |
| |
| def testPrepareWipe(self): |
| self._gooftool._util.GetReleaseRootPartitionPath( |
| ).AndReturn("root1") |
| self._gooftool._util.FindAndRunScript("prepare_wipe.sh", ["root1"], []) |
| |
| self._gooftool._util.GetReleaseRootPartitionPath( |
| ).AndReturn("root2") |
| self._gooftool._util.FindAndRunScript("prepare_wipe.sh", ["root2"], |
| ["FACTORY_WIPE_TAGS=fast"]) |
| |
| self.mox.ReplayAll() |
| |
| self._gooftool.PrepareWipe(False) |
| self._gooftool.PrepareWipe(True) |
| |
| def testWriteHWID(self): |
| self._gooftool._crosfw.LoadMainFirmware().MultipleTimes().AndReturn( |
| MockMainFirmware()) |
| self._gooftool._util.shell('gbb_utility --set --hwid="hwid1" "firmware"') |
| self._gooftool._util.shell('gbb_utility --set --hwid="hwid2" "firmware"') |
| |
| self.mox.ReplayAll() |
| |
| self._gooftool.WriteHWID("hwid1") |
| self._gooftool.WriteHWID("hwid2") |
| |
| def testVerifyWPSwitch(self): |
| # 1st call: enabled |
| self._gooftool._util.shell('crossystem wpsw_cur').AndReturn(StubStdout('1')) |
| # 2nd call: disabled |
| self._gooftool._util.shell('crossystem wpsw_cur').AndReturn(StubStdout('0')) |
| |
| self.mox.ReplayAll() |
| |
| self._gooftool.VerifyWPSwitch() |
| self.assertRaises(Error, self._gooftool.VerifyWPSwitch) |
| |
| def _SetupBrandingMocks(self, ro_vpd, fake_rootfs_path): |
| """Set up mocks for VerifyBranding tests. |
| |
| Args: |
| ro_vpd: The dictionary to use for the RO VPD. |
| fake_rootfs_path: A path at which we pretend to mount the release rootfs. |
| """ |
| |
| # Fake partition to return from MountPartition mock. |
| @contextmanager |
| def MockPartition(path): |
| yield path |
| |
| self.mox.StubOutWithMock(vpd.ro, "GetAll") |
| self.mox.StubOutWithMock(gooftool, "MountPartition") |
| |
| vpd.ro.GetAll().AndReturn(ro_vpd) |
| if fake_rootfs_path: |
| # Pretend that '/dev/rel' is the release rootfs path. |
| self._gooftool._util.GetReleaseRootPartitionPath().AndReturn('/dev/rel') |
| # When '/dev/rel' is mounted, return a context manager yielding |
| # fake_rootfs_path. |
| gooftool.MountPartition('/dev/rel').AndReturn( |
| MockPartition(fake_rootfs_path)) |
| |
| def testVerifyBranding_NoBrandCode(self): |
| self._SetupBrandingMocks({}, '/doesntexist') |
| self.mox.ReplayAll() |
| # Should fail, since rlz_brand_code isn't present anywhere |
| self.assertRaisesRegexp(ValueError, 'rlz_brand_code is not present', |
| self._gooftool.VerifyBranding) |
| |
| def testVerifyBranding_AllInVPD(self): |
| self._SetupBrandingMocks( |
| dict(rlz_brand_code='ABCD', customization_id='FOO'), None) |
| self.mox.ReplayAll() |
| self.assertEquals(dict(rlz_brand_code='ABCD', customization_id='FOO'), |
| self._gooftool.VerifyBranding()) |
| |
| def testVerifyBranding_BrandCodeInVPD(self): |
| self._SetupBrandingMocks(dict(rlz_brand_code='ABCD'), None) |
| self.mox.ReplayAll() |
| self.assertEquals(dict(rlz_brand_code='ABCD', customization_id=None), |
| self._gooftool.VerifyBranding()) |
| |
| def testVerifyBranding_BrandCodeInRootFS(self): |
| with file_utils.TempDirectory() as tmp: |
| # Create a /opt/oem/etc/BRAND_CODE file within the fake mounted rootfs. |
| rlz_brand_code_path = os.path.join( |
| tmp, branding.BRAND_CODE_PATH.lstrip('/')) |
| file_utils.TryMakeDirs(os.path.dirname(rlz_brand_code_path)) |
| with open(rlz_brand_code_path, 'w') as f: |
| f.write('ABCD') |
| |
| self._SetupBrandingMocks({}, tmp) |
| self.mox.ReplayAll() |
| self.assertEquals(dict(rlz_brand_code='ABCD', customization_id=None), |
| self._gooftool.VerifyBranding()) |
| |
| def testVerifyBranding_BadBrandCode(self): |
| self._SetupBrandingMocks(dict(rlz_brand_code='ABCDx', |
| customization_id='FOO'), None) |
| self.mox.ReplayAll() |
| self.assertRaisesRegexp(ValueError, 'Bad format for rlz_brand_code', |
| self._gooftool.VerifyBranding) |
| |
| def testVerifyBranding_BadConfigurationId(self): |
| self._SetupBrandingMocks(dict(rlz_brand_code='ABCD', |
| customization_id='FOOx'), None) |
| self.mox.ReplayAll() |
| self.assertRaisesRegexp(ValueError, 'Bad format for customization_id', |
| self._gooftool.VerifyBranding) |
| |
| def testCheckDevSwitchForDisabling(self): |
| # 1st call: virtual switch |
| self._gooftool._util.GetVBSharedDataFlags().AndReturn(0x400) |
| |
| # 2nd call: dev mode disabled |
| self._gooftool._util.GetVBSharedDataFlags().AndReturn(0) |
| self._gooftool._util.GetCurrentDevSwitchPosition().AndReturn(0) |
| |
| # 3rd call: dev mode enabled |
| self._gooftool._util.GetVBSharedDataFlags().AndReturn(0) |
| self._gooftool._util.GetCurrentDevSwitchPosition().AndReturn(1) |
| |
| self.mox.ReplayAll() |
| self.assertTrue(self._gooftool.CheckDevSwitchForDisabling()) |
| self.assertFalse(self._gooftool.CheckDevSwitchForDisabling()) |
| self.assertRaises(Error, self._gooftool.CheckDevSwitchForDisabling) |
| |
| def testSetFirmwareBitmapLocalePass(self): |
| '''Test for a normal process of setting firmware bitmap locale.''' |
| |
| # Stub data from VPD for en. |
| self._gooftool._crosfw.LoadMainFirmware().AndReturn(MockMainFirmware()) |
| self._gooftool._read_ro_vpd("firmware").AndReturn( |
| {'initial_locale': 'zh-TW'}) |
| self._gooftool._named_temporary_file().AndReturn(MockFile()) |
| self._gooftool._util.shell('gbb_utility -g --bmpfv=filename firmware') |
| |
| # Stub for multiple available locales in the firmware bitmap. |
| self._gooftool._unpack_bmpblock('read_results').AndReturn( |
| {'locales': ['ja', 'zh', 'en']}) |
| |
| # Expect index = 1 for zh is matched. |
| self._gooftool._util.shell('crossystem loc_idx=1') |
| |
| self.mox.ReplayAll() |
| self._gooftool.SetFirmwareBitmapLocale() |
| |
| def testSetFirmwareBitmapLocaleNoMatch(self): |
| """Test for setting firmware bitmap locale without matching default locale. |
| """ |
| |
| # Stub data from VPD for en. |
| self._gooftool._crosfw.LoadMainFirmware().AndReturn(MockMainFirmware()) |
| self._gooftool._read_ro_vpd("firmware").AndReturn( |
| {'initial_locale': 'en'}) |
| self._gooftool._named_temporary_file().AndReturn(MockFile()) |
| self._gooftool._util.shell('gbb_utility -g --bmpfv=filename firmware') |
| |
| # Stub for multiple available locales in the firmware bitmap, but missing |
| # 'en'. |
| self._gooftool._unpack_bmpblock('read_results').AndReturn( |
| {'locales': ['ja', 'fr', 'zh']}) |
| |
| self.mox.ReplayAll() |
| self.assertRaises(Error, self._gooftool.SetFirmwareBitmapLocale) |
| |
| def testSetFirmwareBitmapLocaleNoVPD(self): |
| '''Test for setting firmware bitmap locale without default locale in VPD.''' |
| |
| # VPD has no locale data. |
| self._gooftool._crosfw.LoadMainFirmware().AndReturn(MockMainFirmware()) |
| self._gooftool._read_ro_vpd("firmware").AndReturn({}) |
| |
| self.mox.ReplayAll() |
| self.assertRaises(Error, self._gooftool.SetFirmwareBitmapLocale) |
| |
| def testGetSystemDetails(self): |
| '''Test for GetSystemDetails to ensure it returns desired keys.''' |
| |
| self._gooftool._util.shell(mox.IsA(str)).MultipleTimes().AndReturn( |
| StubStdout("stub_value")) |
| self._gooftool._util.GetCrosSystem().AndReturn({'key':'value'}) |
| |
| self.mox.ReplayAll() |
| self.assertEquals( |
| set(['platform_name', 'crossystem', 'modem_status', 'ec_wp_status', |
| 'bios_wp_status']), |
| set(self._gooftool.GetSystemDetails().keys())) |
| |
| |
| if __name__ == '__main__': |
| logging.basicConfig(level=logging.INFO) |
| unittest.main() |