autotest/factory: merge ToT changes to R12 factory, to support latest HWID

autotest/factory: separate hwid-matching and component test
Cherry-picked: http://gerrit.chromium.org/gerrit/2867

factory_ProbeHWID: Support simple UI to manually select HWID
Cherry-picked: http://gerrit.chromium.org/gerrit/5001

factory_ProbeHWID: improve error feedback in report
Cherry-picked: http://gerrit.chromium.org/gerrit/5551

BUG=none, prepare for next factory bundle release
TEST=none, factory will verify

Change-Id: I6fe9d123dadc10037659298c089b3a898914b03a
Reviewed-on: http://gerrit.chromium.org/gerrit/5648
Reviewed-by: Jay Kim <yongjaek@chromium.org>
Tested-by: Hung-Te Lin <hungte@chromium.org>
diff --git a/client/site_tests/factory_Finalize/factory_Finalize.py b/client/site_tests/factory_Finalize/factory_Finalize.py
index 9a51554..ce42a1e 100644
--- a/client/site_tests/factory_Finalize/factory_Finalize.py
+++ b/client/site_tests/factory_Finalize/factory_Finalize.py
@@ -99,15 +99,10 @@
         # solve upload file names
         upload_method = self.normalize_upload_method(upload_method)
 
-        # build parameters
-        db_path = os.path.join(self.bindir,
-                               '..',
-                               'hardware_Components',
-                               'data_*/components*')
         args = ['gooftool',
                 '--finalize',
                 '--verbose',
-                '--db_path "%s"' % db_path,
+                '--wipe_method "%s"' % ('secure' if secure_wipe else 'fast'),
                 '--upload_method "%s"' % upload_method,
                 ]
         if not check_and_enable_write_protect:
diff --git a/client/site_tests/factory_ProbeHWID/factory_ProbeHWID.py b/client/site_tests/factory_ProbeHWID/factory_ProbeHWID.py
new file mode 100644
index 0000000..e952dc1
--- /dev/null
+++ b/client/site_tests/factory_ProbeHWID/factory_ProbeHWID.py
@@ -0,0 +1,207 @@
+# Copyright (c) 2011 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.
+
+import glob
+import gtk
+import os
+import re
+from gtk import gdk
+
+from autotest_lib.client.bin import factory
+from autotest_lib.client.bin import factory_ui_lib as ful
+from autotest_lib.client.bin import test
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.cros import gooftools
+
+
+class factory_ProbeHWID(test.test):
+    version = 4
+    SELECTION_PER_PAGE = 10
+    HWID_AUTODETECT = None
+
+    def probe_hwid(self):
+        """ Finds out the matching HWID by detection.
+            This function must not use any GUI resources.
+        """
+        command = 'gooftool --probe --verbose'
+        pattern = 'Probed: '
+
+        (stdout, stderr, result) = gooftools.run(command, ignore_status=True)
+
+        # Decode successfully matched results
+        hwids = [hwid.lstrip(pattern)
+                 for hwid in stdout.splitlines()
+                 if hwid.startswith(pattern)]
+
+        # Decode unmatched results.
+        # Sample output:
+        #  Unmatched for /usr/local/share/chromeos-hwid/components_BLAHBLAH:
+        #  { 'part_id_3g': ['Actual: XXX', 'Expected: YYY']}
+        #  Current System:
+        #  { 'part_id_xxx': ['yyy'] },
+        str_unmatched = 'Unmatched '
+        str_current = 'Current System:'
+
+        start = stderr.find(str_unmatched)
+        if start < 0:
+            start = 0
+        end = stderr.rfind(str_current)
+        if end < 0:
+            unmatched = stderr[start:]
+        else:
+            unmatched = stderr[start:end]
+        # TODO(hungte) Sort and find best match candidate
+        unmatched = '\n'.join([line for line in unmatched.splitlines()
+                               # 'gft_hwcomp' or 'probe' are debug message.
+                               if not (line.startswith('gft_hwcomp:') or
+                                       line.startswith('probe:') or
+                                       (not line))])
+        # Report the results
+        if len(hwids) < 1:
+            raise error.TestFail('\n'.join(('No HWID matched.', unmatched)))
+        if len(hwids) > 1:
+            raise error.TestError('Multiple HWIDs match current system: ' +
+                                  ','.join(hwids))
+        if result != 0:
+            raise error.TestFail('HWID matched (%s) with unknown error: %s'
+                                 % hwids[0], result)
+        return hwids[0]
+
+    def update_hwid(self, path_to_file):
+        """ Updates component list file to shared data LAST_PROBED_HWID_NAME,
+            and then let factory_WriteGBB to update system. factory_Finalize
+            will verify if that's set correctly.
+            This function must not use any GUI resources.
+
+            TODO(hungte) Merge factory_WriteGBB into this test
+
+        Args:
+            path_to_component_list: A component list file containing HWID and
+            GBB information. Use HWID_AUTODETECT for detection.
+        """
+        if path_to_file == self.HWID_AUTODETECT:
+            path_to_file = self.probe_hwid()
+        # Set the factory state sharead data for factory_WriteGBB
+        factory.log('Set factory state shared data %s = %s' %
+                    (factory.LAST_PROBED_HWID_NAME, path_to_file))
+        factory.set_shared_data(factory.LAST_PROBED_HWID_NAME, path_to_file)
+
+    def build_hwid_list(self):
+        files = glob.glob('/usr/local/share/chromeos-hwid/components*')
+        if not files:
+            files = glob.glob('/usr/share/chromeos-hwid/components*')
+        files.sort()
+
+        if not files:
+            raise error.TestError('No HWID component files found on system.')
+
+        # part_id_hwqual is required for every component list file.
+        hwids = [(eval(open(hwid_file).read())['part_id_hwqual'][0], hwid_file)
+                 for hwid_file in files ]
+
+        # Add special entries
+        special_hwids = [('<Auto Detect>', self.HWID_AUTODETECT)]
+        for hwid in hwids:
+            if hwid[0] != self.current_hwid:
+                continue
+            special_hwids += [("<Current Value: %s>" % hwid[0], hwid[1])]
+            break
+
+        return special_hwids + hwids
+
+    def key_release_callback(self, widget, event):
+        if self.writing:
+            return True
+
+        # Process page navigation
+        KEY_PREV = [65361, 65362, ord('h'), ord('k')]  # Left, Up
+        KEY_NEXT = [65363, 65364, ord('l'), ord('j')]  # Right, Down
+        if event.keyval in KEY_PREV:
+            if self.page_index > 0:
+                self.page_index -= 1
+            self.render_page()
+            return True
+        if event.keyval in KEY_NEXT:
+            if self.page_index < self.pages - 1:
+                self.page_index += 1
+            self.render_page()
+            return True
+
+        char = chr(event.keyval) if event.keyval in range(32,127) else  None
+        factory.log('key_release %s(%s)' % (event.keyval, char))
+        try:
+            select = int(char)
+        except ValueError:
+            factory.log('Need a number.')
+            return True
+
+        select = select + self.page_index * self.SELECTION_PER_PAGE
+        if select < 0 or select >= len(self.hwid_list):
+            factory.log('Invalid selection: %d' % select)
+            return True
+
+        data = self.hwid_list[select]
+        hwid_file = data[1]
+        if hwid_file == self.HWID_AUTODETECT:
+            self.label.set_text('Probing HWID, Please wait... (may take >30s)')
+            self.writing = True
+            gtk.main_iteration(False)  # try to update screen
+        elif hwid_file:
+            factory.log('Selected: %s' % ', '.join(data).replace('\n', ' '))
+
+        try:
+            self.update_hwid(hwid_file)
+        except Exception, e:
+            self._fail_msg = '%s' % e
+
+        gtk.main_quit()
+        return True
+
+    def register_callbacks(self, window):
+        window.connect('key-release-event', self.key_release_callback)
+        window.add_events(gdk.KEY_RELEASE_MASK)
+
+    def render_page(self):
+        msg = 'Choose a HWID:\n\n'
+        start = self.page_index * self.SELECTION_PER_PAGE
+        end = start + self.SELECTION_PER_PAGE
+        for index, data in enumerate(self.hwid_list[start:end]):
+            msg += '%s) %s\n\n' % (index, data[0])
+        if self.pages > 1:
+            msg += '[Page %d / %d, navigate with arrow keys]' % (
+                    self.page_index + 1, self.pages)
+        self.label.set_text(msg)
+
+    def run_once(self, autodetect=True):
+        factory.log('%s run_once' % self.__class__)
+        self._fail_msg = None
+
+        if autodetect:
+            self.update_hwid(self.HWID_AUTODETECT)
+        else:
+            # TODO(hungte) add timeout
+            self.page_index = 0
+            self.pages = 0
+            self.writing = False
+            with os.popen("crossystem hwid 2>/dev/null", "r") as hwid_proc:
+                self.current_hwid = hwid_proc.read()
+
+            self.hwid_list = self.build_hwid_list()
+            self.pages = len(self.hwid_list) / self.SELECTION_PER_PAGE
+            if len(self.hwid_list) % self.SELECTION_PER_PAGE:
+                self.pages += 1
+
+            self.label = ful.make_label('')
+            test_widget = gtk.EventBox()
+            test_widget.modify_bg(gtk.STATE_NORMAL, ful.BLACK)
+            test_widget.add(self.label)
+            self.render_page()
+
+            ful.run_test_widget(
+                    self.job, test_widget,
+                    window_registration_callback=self.register_callbacks)
+
+        factory.log('%s run_once finished' % repr(self.__class__))
+        if self._fail_msg:
+            raise error.TestFail(self._fail_msg)
diff --git a/client/site_tests/hardware_Components/common_data/devkeys/firmware_data_key.vbpubk b/client/site_tests/hardware_Components/common_data/devkeys/firmware_data_key.vbpubk
deleted file mode 100644
index 804d566..0000000
--- a/client/site_tests/hardware_Components/common_data/devkeys/firmware_data_key.vbpubk
+++ /dev/null
Binary files differ
diff --git a/client/site_tests/hardware_Components/common_data/devkeys/kernel_data_key.vbpubk b/client/site_tests/hardware_Components/common_data/devkeys/kernel_data_key.vbpubk
deleted file mode 100644
index 80910b7..0000000
--- a/client/site_tests/hardware_Components/common_data/devkeys/kernel_data_key.vbpubk
+++ /dev/null
Binary files differ
diff --git a/client/site_tests/hardware_Components/common_data/devkeys/kernel_subkey.vbpubk b/client/site_tests/hardware_Components/common_data/devkeys/kernel_subkey.vbpubk
deleted file mode 100644
index a24a036..0000000
--- a/client/site_tests/hardware_Components/common_data/devkeys/kernel_subkey.vbpubk
+++ /dev/null
Binary files differ
diff --git a/client/site_tests/hardware_Components/common_data/devkeys/recovery_kernel_data_key.vbpubk b/client/site_tests/hardware_Components/common_data/devkeys/recovery_kernel_data_key.vbpubk
deleted file mode 100644
index 3e9e67c..0000000
--- a/client/site_tests/hardware_Components/common_data/devkeys/recovery_kernel_data_key.vbpubk
+++ /dev/null
Binary files differ
diff --git a/client/site_tests/hardware_Components/common_data/devkeys/recovery_key.vbpubk b/client/site_tests/hardware_Components/common_data/devkeys/recovery_key.vbpubk
deleted file mode 100644
index 86d2471..0000000
--- a/client/site_tests/hardware_Components/common_data/devkeys/recovery_key.vbpubk
+++ /dev/null
Binary files differ
diff --git a/client/site_tests/hardware_Components/common_data/devkeys/root_key.vbpubk b/client/site_tests/hardware_Components/common_data/devkeys/root_key.vbpubk
deleted file mode 100644
index dfca9d0..0000000
--- a/client/site_tests/hardware_Components/common_data/devkeys/root_key.vbpubk
+++ /dev/null
Binary files differ
diff --git a/client/site_tests/hardware_Components/hardware_Components.py b/client/site_tests/hardware_Components/hardware_Components.py
index 0d8d310..5e8fb07 100644
--- a/client/site_tests/hardware_Components/hardware_Components.py
+++ b/client/site_tests/hardware_Components/hardware_Components.py
@@ -15,38 +15,20 @@
 class hardware_Components(test.test):
     version = 3
 
-    def run_once(self, approved_dbs='approved_components', do_probe=True):
-        # In probe mode, we have to find out the matching HWID, write that into
-        # shared data LAST_PROBED_HWID_NAME, and then let factory_WriteGBB to
-        # update system. factory_Finalize will verify if that's set correctly.
-        #
-        # In verify mode, we simply check if current system matches a hardware
-        # configuration in the databases.
+    def run_once(self, approved_dbs='approved_components'):
+        # Checks if current system matches a hardware configuration in the
+        # databases.
+        # Currently the files are expected to be inside same folder with
+        # hardware_Components test.
 
-        last_probed_hwid = None
-        if not do_probe:
-            # Verify, or trust previous probed HWID.
-            try:
-                last_probed_hwid = factory.get_shared_data(
-                        factory.LAST_PROBED_HWID_NAME)
-            except Exception, e:
-                # hardware_Components may run without factory environment
-                factory.log('Failed getting shared data, ignored: %s' % repr(e))
-
-        # If a hwid was probed, trust it. Otherwise, find best match.
-        if last_probed_hwid:
-            approved_dbs = last_probed_hwid
-        else:
-            # Currently the files are expected to be inside same folder with
-            # hardware_Components test.
-            approved_dbs = os.path.join(self.bindir, approved_dbs)
-            sample_approved_dbs = os.path.join(self.bindir,
-                                               'approved_components.default')
-            if (not glob.glob(approved_dbs)) and glob.glob(sample_approved_dbs):
-                # Fallback to the default (sample) version
-                approved_dbs = sample_approved_dbs
-                factory.log('Using default (sample) approved component list: %s'
-                            % sample_approved_dbs)
+        approved_dbs = os.path.join(self.bindir, approved_dbs)
+        sample_approved_dbs = os.path.join(self.bindir,
+                                           'approved_components.default')
+        if (not glob.glob(approved_dbs)) and glob.glob(sample_approved_dbs):
+            # Fallback to the default (sample) version
+            approved_dbs = sample_approved_dbs
+            factory.log('Using default (sample) approved component list: %s'
+                        % sample_approved_dbs)
 
         # approved_dbs supports shell-like filename expansion.
         existing_dbs = glob.glob(approved_dbs)
@@ -54,15 +36,10 @@
             raise error.TestError('Unable to find approved db: %s' %
                                   approved_dbs)
 
-        if do_probe:
-            command = 'gooftool --probe --db_path "%s" --verbose' % approved_dbs
-            pattern = 'Probed: '
-            # The output format is "Probed: PATH"
-        else:
-            command = ('gooftool --verify_hwid --db_path "%s" --verbose' %
-                       approved_dbs)
-            pattern = 'Verified: '
-            # The output format is "Verified: PATH (HWID)", not a pure path.
+        command = ('gooftool --verify_hwid --db_path "%s" --verbose' %
+                   approved_dbs)
+        pattern = 'Verified: '
+        # The output format is "Verified: PATH (HWID)", not a pure path.
 
         (stdout, stderr, result) = gooftools.run(command, ignore_status=True)
 
@@ -96,15 +73,4 @@
             raise error.TestFail('HWID matched (%s) with unknown error: %s'
                                  % hwids[0], result)
 
-        # Set the factory state sharead data for factory_WriteGBB
-        if do_probe:
-            factory.log('Set factory state shared data %s = %s' %
-                        (factory.LAST_PROBED_HWID_NAME, hwids[0]))
-            try:
-                factory.set_shared_data(factory.LAST_PROBED_HWID_NAME,
-                                        hwids[0])
-            except Exception, e:
-                # hardware_Components may run without factory environment
-                factory.log('Failed setting shared data, ignored: %s' %
-                            repr(e))
         factory.log('Exact Matched: HWID=%s' % hwids[0])
diff --git a/client/site_tests/suite_Factory/test_list b/client/site_tests/suite_Factory/test_list
index 7462728..9f8000e 100644
--- a/client/site_tests/suite_Factory/test_list
+++ b/client/site_tests/suite_Factory/test_list
@@ -209,9 +209,8 @@
             AutomatedSubTest(
                 label_en='hw-qual-id matching',
                 label_zw='型號匹配',
-                autotest_name='hardware_Components',
-                unique_name='hw-qual-id-match',
-                dargs={'approved_dbs':'data_*/components*'}),
+                autotest_name='factory_ProbeHWID',
+                unique_name='hw-qual-id-match'),
 
             AutomatedSubTest(
                 label_en='write GBB',