| # Copyright 2012 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Uses HWID v3 to generate, encode, and verify the device's HWID. |
| |
| Description |
| ----------- |
| This test generates and verifies HWID of device under testing. |
| |
| Test Procedure |
| -------------- |
| This test does not require operator interaction. |
| When ``generate`` is ``True``, this test will do the following: |
| |
| 1. If ``enable_factory_server`` is ``True``, it downloads latest HWID database |
| from Google Factory Server. |
| 2. Collect materials (including probed results, device data, and optionally |
| the vpd data) from DUT for generating the HWID string. This step is |
| equivalent to executing ``hwid collect-material`` in shell. |
| 3. Generate HWID by command ``hwid generate --probed-results-file |
| <probed-results> --material-file <hwid-material-file> --json-output``. |
| 4. Verify generated HWID by ``hwid verify --material-file <hwid-material-file> |
| --phase <phase>``. |
| 5. Write HWID to GBB by ``hwid write <generated-hwid>``. |
| |
| If ``generate`` is ``False``, then instead of running ``hwid generate`` in step |
| 3, it will just use ``hwid read`` to read saved HWID from the device. And step |
| 5 will be skipped. |
| |
| If ``vpd_data_file`` is set to a string of ``<path>``, the vpd-related |
| arguments for ``hwid`` tool will be ``--vpd-data-file <path>``; otherwise if |
| ``run_vpd`` is ``True``, the vpd-related arguments for ``hwid`` tool will be |
| ``--run-vpd``. Note that ``run_vpd=True`` has no effect if ``vpd_data_file`` |
| is set. |
| |
| Dependency |
| ---------- |
| Various of system utilities like ``vpd`` and ``flashrom`` will be invoked to |
| grab materials from DUT. |
| |
| Examples |
| -------- |
| To generate and verify HWID, add this to your test list:: |
| |
| { |
| "pytest_name": "hwid", |
| "label": "Write HWID" |
| } |
| |
| If you are doing RMA, to allow ``deprecated`` components, you need to enable RMA |
| mode:: |
| |
| { |
| "pytest_name": "hwid", |
| "label": "Write HWID", |
| "args": { |
| "rma_mode": true |
| } |
| } |
| |
| New HWID with 'configless' format is still under testing. To enable this |
| feature, set argument like this:: |
| |
| { |
| "pytest_name": "hwid", |
| "label": "Write HWID", |
| "args": { |
| "enable_configless_fields": true |
| } |
| } |
| |
| To override the default project name, use ``project`` arguments:: |
| |
| { |
| "pytest_name": "hwid", |
| "label": "Write HWID", |
| "args": { |
| "project": "nirwen" |
| } |
| } |
| """ |
| |
| import json |
| import logging |
| |
| import yaml |
| |
| from cros.factory.device import device_utils |
| from cros.factory.test import device_data |
| from cros.factory.test.i18n import _ |
| from cros.factory.test.rules import phase |
| from cros.factory.test import session |
| from cros.factory.test import test_case |
| from cros.factory.test.utils import deploy_utils |
| from cros.factory.test.utils import update_utils |
| from cros.factory.testlog import testlog |
| from cros.factory.utils.arg_utils import Arg |
| from cros.factory.utils import file_utils |
| |
| |
| class HWIDV3Test(test_case.TestCase): |
| """A test for generating and verifying HWID v3.""" |
| related_components = (test_case.TestCategory.HARDWARE_ID, ) |
| |
| ARGS = [ |
| Arg('generate', bool, |
| 'Generate and write the HWID (if False, only verify it).', |
| default=True), |
| Arg('enable_factory_server', bool, |
| 'Update hwid data from factory server.', default=True), |
| Arg('run_vpd', bool, |
| 'Run the `vpd` commandline tool to get the vpd data.', default=False), |
| Arg('vpd_data_file', str, 'Read the specified file to get the vpd data.', |
| default=None), |
| Arg('rma_mode', bool, |
| 'Enable rma_mode, do not check for deprecated components.', |
| default=False), |
| Arg('verify_checksum', bool, 'Enable database checksum verification.', |
| default=True), |
| Arg('enable_configless_fields', bool, 'Include the configless fields', |
| default=False), |
| Arg('enable_component_status_check_on_pvt', bool, |
| 'Enable component status check if the phase is PVT.', default=True), |
| Arg('include_brand_code', bool, 'Include RLZ brand code', default=True), |
| Arg('project', str, 'Project name of the HWID.', default=None), |
| ] |
| |
| def setUp(self): |
| self._dut = device_utils.CreateDUTInterface() |
| self.factory_tools = deploy_utils.CreateFactoryTools(self._dut) |
| self.tmpdir = self._dut.temp.mktemp(is_dir=True, prefix='hwid') |
| |
| def tearDown(self): |
| self._dut.Call(['rm', '-rf', self.tmpdir]) |
| |
| def AppendProjectArg(self, cmd): |
| """Append the project name to the command list if the name is not None.""" |
| # yapf: disable |
| if self.args.project: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| # yapf: disable |
| cmd += ['--project', self.args.project] # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| |
| def BuildCollectMaterialCommand(self, hwid_material_file: str): |
| """Build the command for `hwid collect-material`. |
| |
| Args: |
| hwid_material_file: The path of hwid_material_file on DUT. |
| |
| Returns: |
| A list of arguments in the command. |
| """ |
| # pass device info to DUT |
| device_info_file = self._dut.path.join(self.tmpdir, 'device_info') |
| device_info = device_data.GetAllDeviceData() |
| with file_utils.UnopenedTemporaryFile() as f: |
| with open(f, 'w', encoding='utf8') as fp: |
| yaml.safe_dump(device_info, fp) |
| self._dut.SendFile(f, device_info_file) |
| |
| collect_material_cmd = ['hwid', 'collect-material'] |
| collect_material_cmd.extend(['--device-info-file', device_info_file]) |
| # yapf: disable |
| if self.args.vpd_data_file: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| # yapf: disable |
| collect_material_cmd.extend(['--vpd-data-file', self.args.vpd_data_file]) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| # yapf: disable |
| if self.args.run_vpd: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| collect_material_cmd.append('--run-vpd') |
| |
| collect_material_cmd.extend(['--output-file', hwid_material_file]) |
| return collect_material_cmd |
| |
| def runTest(self): |
| testlog.LogParam(name='phase', value=str(phase.GetPhase())) |
| # yapf: disable |
| phase.AssertStartingAtPhase(phase.EVT, self.args.verify_checksum, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| 'HWID checksum must be verified') |
| phase.AssertStartingAtPhase( |
| # yapf: disable |
| phase.PVT, self.args.project is None, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| 'Should not use `project` option in this phase') |
| |
| # yapf: disable |
| if self.args.enable_factory_server: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| update_utils.UpdateHWIDDatabase(self._dut) |
| |
| # yapf: disable |
| self.ui.SetState(_('Collecting DUT materials...')) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| hwid_material_file = self._dut.path.join(self.tmpdir, 'hwid_material_file') |
| collect_material_cmd = self.BuildCollectMaterialCommand(hwid_material_file) |
| self.factory_tools.Call(collect_material_cmd, log=True) |
| |
| hwid_material = self._dut.ReadFile(hwid_material_file) |
| testlog.LogParam(name='hwid_material', value=hwid_material) |
| testlog.UpdateParam(name='hwid_material', |
| description='materials to generate HWID string') |
| |
| # yapf: disable |
| if self.args.generate: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| # yapf: disable |
| self.ui.SetState(_('Generating HWID (v3)...')) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| generate_cmd = [ |
| 'hwid', 'generate', '--material-file', hwid_material_file, |
| '--json-output' |
| ] |
| # yapf: disable |
| if self.args.rma_mode: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| generate_cmd += ['--rma-mode'] |
| # yapf: disable |
| if not self.args.verify_checksum: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| generate_cmd += ['--no-verify-checksum'] |
| # yapf: disable |
| if self.args.enable_configless_fields: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| generate_cmd += ['--with-configless-fields'] |
| # yapf: disable |
| if not self.args.include_brand_code: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| generate_cmd += ['--no-brand-code'] |
| self.AppendProjectArg(generate_cmd) |
| |
| output = self.factory_tools.CallOutput(generate_cmd) |
| self.assertIsNotNone(output, 'HWID generate failed.') |
| hwid = json.loads(output) |
| |
| encoded_string = hwid['encoded_string'] |
| session.console.info('Generated HWID: %s', encoded_string) |
| |
| # try to decode HWID |
| decode_cmd = ['hwid', 'decode'] |
| self.AppendProjectArg(decode_cmd) |
| decode_cmd += [encoded_string] |
| decoded_hwid = self.factory_tools.CallOutput(decode_cmd) |
| self.assertIsNotNone(decoded_hwid, 'HWID decode failed.') |
| |
| logging.info('HWID Database checksum: %s', hwid['database_checksum']) |
| |
| testlog.LogParam(name='generated_hwid', value=encoded_string) |
| testlog.LogParam(name='database_checksum', |
| value=hwid['database_checksum']) |
| testlog.LogParam(name='decoded_hwid', value=decoded_hwid) |
| |
| device_data.UpdateDeviceData({'hwid': encoded_string}) |
| else: |
| read_cmd = ['hwid', 'read'] |
| self.AppendProjectArg(read_cmd) |
| encoded_string = self.factory_tools.CheckOutput(read_cmd).strip() |
| |
| # yapf: disable |
| self.ui.SetState( # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| _('Verifying HWID (v3): {encoded_string}...', |
| encoded_string=(encoded_string or _('(unchanged)')))) |
| |
| verify_cmd = [ |
| 'hwid', 'verify', '--material-file', hwid_material_file, '--phase', |
| str(phase.GetPhase()) |
| ] |
| # yapf: disable |
| if self.args.rma_mode: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| verify_cmd += ['--rma-mode'] |
| # yapf: disable |
| if not self.args.verify_checksum: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| verify_cmd += ['--no-verify-checksum'] |
| # yapf: disable |
| if not self.args.enable_component_status_check_on_pvt: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| verify_cmd += ['--no-pvt-component-status-check'] |
| self.AppendProjectArg(verify_cmd) |
| verify_cmd += [encoded_string] |
| |
| output = self.factory_tools.CheckOutput(verify_cmd) |
| self.assertTrue('Verification passed.' in output) |
| testlog.LogParam(name='verified_hwid', value=encoded_string) |
| |
| # yapf: disable |
| if self.args.generate: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| # yapf: disable |
| self.ui.SetState( # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long |
| # yapf: enable |
| _('Setting HWID (v3): {encoded_string}...', |
| encoded_string=encoded_string)) |
| write_cmd = ['hwid', 'write'] |
| self.AppendProjectArg(write_cmd) |
| write_cmd += [encoded_string] |
| self.factory_tools.CheckCall(write_cmd) |