More unit testing support for HWID.

Add verify_hwid --probe_results flag.  This allows a user to probe a
real device (using 'gooftool probe --include_vpd') and verify the
results offline from a development environment.  This also enables
unit tests that run 'gooftool verify_hwid' without access to a real
device.

Add verify_hwid_unittest.py test.

Add valid_hwids_unittest.py test to check validity and canonical-ness
of HWID database.

CL-DEPEND=CL:*25906
BUG=None
TEST=make lint test

Change-Id: I09095b1b64aece1e2557d77d9b00595c4607a010
Reviewed-on: https://gerrit.chromium.org/gerrit/33976
Commit-Ready: Jon Salz <jsalz@chromium.org>
Reviewed-by: Jon Salz <jsalz@chromium.org>
Tested-by: Jon Salz <jsalz@chromium.org>
diff --git a/py/gooftool/gooftool.py b/py/gooftool/gooftool.py
index 79175f2..fad11e5 100755
--- a/py/gooftool/gooftool.py
+++ b/py/gooftool/gooftool.py
@@ -24,12 +24,13 @@
 
 import factory_common  # pylint: disable=W0611
 
-from cros.factory.common import Error, ParseKeyValueData, SetupLogging, Shell
+from cros.factory.common import Error, SetupLogging, Shell
 from cros.factory.common import YamlWrite
 from cros.factory.gooftool import crosfw
 from cros.factory.gooftool import report_upload
 from cros.factory.gooftool.bmpblk import unpack_bmpblock
 from cros.factory.gooftool.probe import Probe, PROBEABLE_COMPONENT_CLASSES
+from cros.factory.gooftool.probe import ReadRoVpd, ReadRwVpd
 from cros.factory.gooftool.vpd_data import KNOWN_VPD_FIELD_DATA
 from cros.factory.hacked_argparse import CmdArg, Command, ParseCmdline
 from cros.factory.hacked_argparse import verbosity_cmd_arg
@@ -90,19 +91,6 @@
   return script_path
 
 
-def ReadVpd(fw_image_file, kind):
-  raw_vpd_data = Shell('vpd -i %s -l -f %s' % (kind, fw_image_file)).stdout
-  return ParseKeyValueData('"(.*)"="(.*)"$', raw_vpd_data)
-
-
-def ReadRoVpd(fw_image_file):
-  return ReadVpd(fw_image_file, 'RO_VPD')
-
-
-def ReadRwVpd(fw_image_file):
-  return ReadVpd(fw_image_file, 'RW_VPD')
-
-
 # TODO(tammo): Replace calls to sys.exit with raise Exit, and maybe
 # treat that specially (as a smoot exit, as opposed to the more
 # verbose output for generic Error).
@@ -314,12 +302,15 @@
          CmdArg('--no_vol', action='store_true',
                 help='Do not probe volatile data.'),
          CmdArg('--no_ic', action='store_true',
-                help='Do not probe initial_config data.'))
+                help='Do not probe initial_config data.'),
+         CmdArg('--include_vpd', action='store_true',
+                help='Include VPD data in volatiles.'))
 def RunProbe(options):
   """Print yaml-formatted breakdown of probed device properties."""
   probe_results = Probe(target_comp_classes=options.comps,
-                              probe_volatile=not options.no_vol,
-                              probe_initial_config=not options.no_ic)
+                        probe_volatile=not options.no_vol,
+                        probe_initial_config=not options.no_ic,
+                        probe_vpd=options.include_vpd)
   print probe_results.Encode()
 
 
@@ -343,7 +334,7 @@
       sys.exit('ERROR: specified component class %r does not exist'
                ' in the component DB.' % comp_class)
   probe_results = Probe(target_comp_classes=options.target_comps,
-                              probe_volatile=False, probe_initial_config=False)
+                        probe_volatile=False, probe_initial_config=False)
   errors = []
   matches = []
   for comp_class in sorted(options.target_comps):
@@ -370,7 +361,13 @@
 
 @Command('verify_hwid',
          _hwid_status_list_cmd_arg,
-         _hwdb_path_cmd_arg)
+         _hwdb_path_cmd_arg,
+         CmdArg('--probe_results', metavar='RESULTS.yaml',
+                help=('Output from "gooftool probe" (used instead of '
+                      'probing this system).')),
+         CmdArg('--hwid', metavar='HWID',
+                help=('HWID to verify (instead of the currently set HWID of '
+                      'this system)')))
 def VerifyHwid(options):
   """Verify system HWID properties match probed device properties.
 
@@ -383,7 +380,6 @@
   possible verify that values are legitimate.
   """
   def VerifyVpd(ro_vpd_keys, rw_vpd_keys):
-    ro_vpd = ReadRoVpd(main_fw_file)
     for key in ro_vpd_keys:
       if key not in ro_vpd:
         sys.exit('Missing required RO VPD field: %s' % key)
@@ -391,7 +387,6 @@
       value = ro_vpd[key]
       if (known_valid_values is not None) and (value not in known_valid_values):
         sys.exit('Invalid RO VPD entry : key %r, value %r' % (key, value))
-    rw_vpd = ReadRwVpd(main_fw_file)
     for key in rw_vpd_keys:
       if key not in rw_vpd:
         sys.exit('Missing required RW VPD field: %s' % key)
@@ -401,18 +396,38 @@
         sys.exit('Invalid RW VPD entry : key %r, value %r' % (key, value))
     _event_log.Log('vpd', ro_vpd=ro_vpd, rw_vpd=rw_vpd)
   map(hwid_tool.Validate.Status, options.status)
+
   main_fw_file = crosfw.LoadMainFirmware().GetFileName()
-  gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
-  hwid_str = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
+
+  if options.hwid:
+    hwid_str = options.hwid
+  else:
+    gbb_result = Shell('gbb_utility -g --hwid %s' % main_fw_file).stdout
+    hwid_str = re.findall(r'hardware_id:(.*)', gbb_result)[0].strip()
   hwid = hwid_tool.ParseHwid(hwid_str)
   hw_db = hwid_tool.HardwareDb(options.hwdb_path)
-  print 'Verifying system HWID: %r\n' % hwid.hwid
+  print 'Verifying HWID: %r\n' % hwid.hwid
   device = hw_db.GetDevice(hwid.board)
   hwid_status = device.GetHwidStatus(hwid.bom, hwid.variant, hwid.volatile)
   if hwid_status not in options.status:
     sys.exit('HWID status must be one of [%s], found %r' %
              (', '.join(options.status), hwid_status))
-  probe_results = Probe()
+  if options.probe_results:
+    # Pull in probe results (including VPD data) from the given file
+    # rather than probing the current system.
+    probe_results = hwid_tool.ProbeResults.Decode(
+        open(options.probe_results).read())
+    ro_vpd = {}
+    rw_vpd = {}
+    for k, v in probe_results.found_volatile_values.items():
+      match = re.match('^vpd\.(ro|rw)\.(\w+)$', k)
+      if match:
+        del probe_results.found_volatile_values[k]
+        (ro_vpd if match.group(1) == 'ro' else rw_vpd)[match.group(2)] = v
+  else:
+    probe_results = Probe()
+    ro_vpd = ReadRoVpd(main_fw_file)
+    rw_vpd = ReadRwVpd(main_fw_file)
   cooked_components = hw_db.comp_db.MatchComponentProbeValues(
     probe_results.found_probe_value_map)
   cooked_volatiles = device.MatchVolatileValues(